XADMaster/XADRAR5Parser.m
2020-04-23 02:51:04 +03:00

921 lines
26 KiB
Objective-C

/*
* XADRAR5Parser.m
*
* Copyright (c) 2017-present, MacPaw Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#import "XADRAR5Parser.h"
#import "XADRARInputHandle.h"
#import "XADRAR50Handle.h"
#import "XADRARAESHandle.h"
#import "XADCRCHandle.h"
#import "NSDateXAD.h"
#import "CSFileHandle.h"
#import "Crypto/hmac_sha256.h"
#import "Crypto/pbkdf2_hmac_sha256.h"
#define ZeroBlock ((RAR5Block){0})
#define ZeroHeaderBlock ((RAR5HeaderBlock){0})
NSString *RAR5SignatureCannotBeFound=@"RAR5SignatureCannotBeFound";
const off_t RAR5MaximumSFXHeader = 1 << 20; // 1 MB
const off_t RAR5SignatureNotFound = -1;
static uint32_t EncryptRAR5CRC32(uint32_t crc,id context);
static BOOL IsRAR5Signature(const uint8_t *ptr)
{
return ptr[0]=='R' && ptr[1]=='a' && ptr[2]=='r' && ptr[3]=='!' &&
ptr[4]==0x1a && ptr[5]==0x07 && ptr[6]==0x01 && ptr[7]==0x00;
}
static uint64_t ReadRAR5VInt(CSHandle *handle)
{
uint64_t res=0;
int pos=0;
for(;;)
{
uint8_t byte=[handle readUInt8];
uint64_t meaningfulBits = (uint64_t) (byte & 0x7f);
res |= meaningfulBits << pos;
if(!(byte&0x80)) return res;
pos+=7;
}
}
static inline BOOL IsZeroBlock(RAR5Block block) { return block.start==0; }
static inline BOOL IsZeroHeaderBlock(RAR5HeaderBlock block) { return IsZeroBlock(block.block); }
@interface XADRAR5Parser (Multipart)
+(BOOL)isPartOfMultiVolume:(CSHandle *)handle;
@end
@implementation XADRAR5Parser
+(int)requiredHeaderSize
{
return 8;
}
+(BOOL)recognizeFileWithHandle:(CSHandle *)handle firstBytes:(NSData *)data name:(NSString *)name
{
off_t signatureLocation = [self signatureLocationInData:data];
return signatureLocation != RAR5SignatureNotFound;
}
+ (off_t)signatureLocationInData:(NSData *)data {
const uint8_t *bytes=[data bytes];
int length=[data length];
if(length<8) return RAR5SignatureNotFound; // TODO: fix to use correct min size
// for SFXX, RAR Signature can be found not at start, but anywhere in the data
int maxxSearch = MIN(length, RAR5MaximumSFXHeader) - 8;
const uint8_t *sign = bytes;
for (int i =0 ; i < maxxSearch; i++, sign++) {
if(IsRAR5Signature(sign)) {
return i;
}
}
return RAR5SignatureNotFound;
}
+(NSArray *)volumesForHandle:(CSHandle *)handle firstBytes:(NSData *)data name:(NSString *)name
{
// Check if multipart
CSFileHandle *filehandle=[CSFileHandle fileHandleForReadingAtPath:name];
if (![self isPartOfMultiVolume:filehandle]) {
return nil;
}
// New naming scheme. Find the last number in the name, and look for other files
// with the same number of digits in the same location.
NSArray *matches;
if((matches=[name substringsCapturedByPattern:@"^(.*[^0-9])([0-9]+)(.*)\\.(rar|sfx|exe)$" options:REG_ICASE]))
return [self scanForVolumesWithFilename:name
regex:[XADRegex regexWithPattern:[NSString stringWithFormat:@"^%@[0-9]{%ld}%@.(rar|sfx|exe)$",
[[matches objectAtIndex:1] escapedPattern],
(long)[(NSString *)[matches objectAtIndex:2] length],
[[matches objectAtIndex:3] escapedPattern]] options:REG_ICASE]
];
return nil;
}
-(id)init
{
if((self=[super init]))
{
headerkey=nil;
cryptocache=[NSMutableDictionary new];
solidstreams=[NSMutableArray new];
}
return self;
}
-(void)dealloc
{
[headerkey release];
[cryptocache release];
[solidstreams release];
[super dealloc];
}
- (void)readUntilSignature {
CSHandle * signatureSearchingHandle = [[self handle] subHandleToEndOfFileFrom:0];
NSData * data = [signatureSearchingHandle readDataOfLengthAtMost:RAR5MaximumSFXHeader];
off_t signatureLocation = [XADRAR5Parser signatureLocationInData:data];
if (signatureLocation == RAR5SignatureNotFound) {
[NSException raise:RAR5SignatureCannotBeFound format:@"Signature cannot be found %@",[self class]];
}
[self.handle skipBytes:signatureLocation];
}
-(void)parse
{
currsolidstream=nil;
totalsolidsize=0;
NSMutableDictionary *currdict=nil;
NSMutableArray *currparts=[NSMutableArray array];
// TODO: Catch exceptions and emit partial files?
@try
{
[self readUntilSignature];
[self.handle skipBytes:8];
for(;;)
{
RAR5Block block=[self readBlockHeader];
if(IsZeroBlock(block)) break;
CSHandle *handle=block.fh;
switch(block.type)
{
case RAR5HeaderTypeMain: // Main archive header.
[self skipBlock:block];
break;
case RAR5HeaderTypeFile: // File header.
{
NSMutableDictionary *dict=[self readFileBlockHeader:block];
BOOL first=!(block.flags&0x0008);
BOOL last=!(block.flags&0x0010);
XADPath *path1=[currdict objectForKey:XADFileNameKey];
XADPath *path2=[dict objectForKey:XADFileNameKey];
if(currdict && !first && [path1 isEqual:path2])
{
// We have a correct continuation from a previously encountered file header.
[currdict addEntriesFromDictionary:dict];
}
else
{
// Not a continuation, or a broken continuation.
if(currdict)
{
// We had a previous entry, but it did not match. Mark it
// as corrupted and get rid of it.
[self addEntryWithDictionary:currdict
inputParts:currparts isCorrupted:YES];
currparts=[NSMutableArray array];
}
// Set this as the current file being collected.
currdict=dict;
if(!first)
{
// This is not the first part of a new file. Mark as corrupted.
[currdict setObject:[NSNumber numberWithBool:YES] forKey:XADIsCorruptedKey];
}
}
[currparts addObject:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithLongLong:[self endOfBlockHeader:block]],@"Offset",
[NSNumber numberWithLongLong:block.datasize],@"InputLength",
[currdict objectForKey:@"RAR5CRC32"],@"CRC32",
nil]];
if(last)
{
// This is the last part of a file, so get rid of it.
[self addEntryWithDictionary:currdict inputParts:currparts isCorrupted:NO];
currparts=[NSMutableArray array];
currdict=nil;
}
[self skipBlock:block];
}
break;
//case RAR5HeaderTypeService: // Service header.
//break;
case RAR5HeaderTypeEncryption: // Archive encryption header.
{
uint64_t version=ReadRAR5VInt(handle);
if(version!=0) [XADException raiseNotSupportedException];
uint64_t flags=ReadRAR5VInt(handle);
int strength=[handle readUInt8];
NSData *salt=[handle readDataOfLength:16];
NSData *passcheck=nil;
if(flags&0x0001)
{
passcheck=[handle readDataOfLength:8];
//uint32_t extracrc=[handle readUInt32LE];
}
headerkey=[[self encryptionKeyForPassword:[self password]
salt:salt strength:strength passwordCheck:passcheck] retain];
[self skipBlock:block];
}
break;
case RAR5HeaderTypeEnd: // End of archive header.
{
uint64_t flags=ReadRAR5VInt(handle);
if(flags&0x0001)
{
[[self currentHandle] seekToEndOfFile];
[[self handle] skipBytes:8];
[headerkey release];
headerkey=nil;
}
else
{
goto end;
}
}
break;
default:
[self skipBlock:block];
break;
}
}
}
@catch(id exception)
{
@throw;
}
end:
return;
}
-(void)addEntryWithDictionary:(NSMutableDictionary *)dict
inputParts:(NSArray *)parts isCorrupted:(BOOL)iscorrupted
{
if(iscorrupted)
{
[dict setObject:[NSNumber numberWithBool:YES] forKey:XADIsCorruptedKey];
}
// If this is not a solid file, forget the earlier solid
// file list and start over.
bool solid=[[dict objectForKey:XADIsSolidKey] boolValue];
bool isDirectory=[[dict objectForKey:XADIsDirectoryKey] boolValue];
if(!solid && !isDirectory)
{
currsolidstream=[NSMutableArray array];
[solidstreams addObject:currsolidstream];
totalsolidsize=0;
}
// Find the length of the file in the output stream, ignoring symlinks.
NSNumber *length=[dict objectForKey:XADFileSizeKey];
if([dict objectForKey:XADLinkDestinationKey]) length=[NSNumber numberWithInt:0];
// Add the list of input parts to the current file.
[dict setObject:parts forKey:@"RAR5InputParts"];
// Add the current file to the solid file list.
if([length longLongValue]!=0) [currsolidstream addObject:dict];
// Set up solid stream paramters for the current file.
[dict setObject:[NSNumber numberWithInteger:[solidstreams count]-1] forKey:XADSolidObjectKey];
[dict setObject:[NSNumber numberWithLongLong:totalsolidsize] forKey:XADSolidOffsetKey];
if(length) [dict setObject:length forKey:XADSolidLengthKey];
// Calculate and set the total compressed size.
off_t compsize=0;
NSEnumerator *enumerator=[parts objectEnumerator];
NSDictionary *part;
while(part=[enumerator nextObject]) compsize+=[[part objectForKey:@"InputLength"] longLongValue];
[dict setObject:[NSNumber numberWithLongLong:compsize] forKey:XADCompressedSizeKey];
[self addEntryWithDictionary:dict];
totalsolidsize+=[length longLongValue];
}
-(NSMutableDictionary *)readFileBlockHeader:(RAR5Block)block
{
CSHandle *handle=block.fh;
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithLongLong:[self endOfBlockHeader:block]],@"RAR5DataOffset",
[NSNumber numberWithLongLong:block.datasize],@"RAR5DataLength",
nil];
uint64_t flags=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:flags] forKey:@"RAR5Flags"];
if(flags&0x0001) [dict setObject:[NSNumber numberWithBool:YES] forKey:XADIsDirectoryKey];
uint64_t uncompsize=ReadRAR5VInt(handle);
if(!(flags&0x0008) && !(flags&0x0001))
[dict setObject:[NSNumber numberWithUnsignedLongLong:uncompsize] forKey:XADFileSizeKey];
uint64_t attributes=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:attributes] forKey:@"RAR5Attributes"];
if(flags&0x0002)
{
uint32_t modification=[handle readUInt32LE];
[dict setObject:[NSDate dateWithTimeIntervalSince1970:modification] forKey:XADLastModificationDateKey];
}
if(flags&0x0004)
{
uint32_t crc=[handle readUInt32LE];
if(!(flags&0x0001))
[dict setObject:[NSNumber numberWithUnsignedInt:crc] forKey:@"RAR5CRC32"];
}
uint64_t compinfo=ReadRAR5VInt(handle);
if(!(flags&0x0001))
{
int compversion=compinfo&0x3f;
BOOL issolid=(compinfo&0x40)>>6;
int compmethod=(compinfo&0x380)>>7;
int dictshift=(compinfo&0x3c00)>>10;
uint64_t dictsize=0x20000ll<<dictshift;
[dict setObject:[NSNumber numberWithUnsignedLongLong:compinfo] forKey:@"RAR5CompressionInformation"];
[dict setObject:[NSNumber numberWithInt:compversion] forKey:@"RAR5CompressionVersion"];
[dict setObject:[NSNumber numberWithBool:issolid] forKey:XADIsSolidKey];
[dict setObject:[NSNumber numberWithInt:compmethod] forKey:@"RAR5CompressionMethod"];
[dict setObject:[NSNumber numberWithUnsignedLongLong:dictsize] forKey:@"RAR5DictionarySize"];
NSString *methodname=nil;
switch(compmethod)
{
case 0: methodname=@"None"; break;
case 1: methodname=[NSString stringWithFormat:@"Fastest v5.0 (%d)",compversion]; break;
case 2: methodname=[NSString stringWithFormat:@"Fast v5.0 (%d)",compversion]; break;
case 3: methodname=[NSString stringWithFormat:@"Normal v5.0 (%d)",compversion]; break;
case 4: methodname=[NSString stringWithFormat:@"Good v5.0 (%d)",compversion]; break;
case 5: methodname=[NSString stringWithFormat:@"Best v5.0 (%d)",compversion]; break;
}
if(methodname) [dict setObject:[self XADStringWithString:methodname] forKey:XADCompressionNameKey];
}
uint64_t os=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:os] forKey:@"RAR5OS"];
switch(os)
{
case 0: [dict setObject:[self XADStringWithString:@"Windows"] forKey:@"RAR5OSName"]; break;
case 1: [dict setObject:[self XADStringWithString:@"Unix"] forKey:@"RAR5OSName"]; break;
}
uint64_t namelength=ReadRAR5VInt(handle);
NSData *namedata=[handle readDataOfLength:namelength];
[dict setObject:[self XADPathWithData:namedata encodingName:XADUTF8StringEncodingName separators:XADUnixPathSeparator]
forKey:XADFileNameKey];
if(block.extrasize)
{
off_t extraend=block.start+block.headersize;
for(;;)
{
uint64_t size=ReadRAR5VInt(handle);
off_t start=[handle offsetInFile];
uint64_t type=ReadRAR5VInt(handle);
switch(type)
{
case 0x01: // File encryption
{
[dict setObject:[NSNumber numberWithBool:YES] forKey:XADIsEncryptedKey];
uint64_t version=ReadRAR5VInt(handle);
if(version!=0) [XADException raiseNotSupportedException];
uint64_t flags=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:flags] forKey:@"RAR5EncryptionFlags"];
int strength=[handle readUInt8];
[dict setObject:[NSNumber numberWithInt:strength] forKey:@"RAR5EncryptionStrength"];
NSData *salt=[handle readDataOfLength:16];
[dict setObject:salt forKey:@"RAR5EncryptionSalt"];
NSData *iv=[handle readDataOfLength:16];
[dict setObject:iv forKey:@"RAR5EncryptionIV"];
if(flags&0x0001)
{
NSData *passcheck=[handle readDataOfLength:8];
[dict setObject:passcheck forKey:@"RAR5EncryptionCheckData"];
uint32_t extracrc=[handle readUInt32LE];
[dict setObject:[NSNumber numberWithUnsignedInt:extracrc] forKey:@"RAR5EncryptionExtraCRC"];
}
if(flags&0x0002)
{
[dict setObject:[NSNumber numberWithBool:YES] forKey:@"RAR5ChecksumsAreEncrypted"];
}
}
break;
case 0x02: // File hash
{
uint64_t type=ReadRAR5VInt(handle);
switch(type)
{
case 0x00:
{
NSData *hash=[handle readDataOfLength:32];
[dict setObject:hash forKey:@"RAR5BLAKE2spHash"];
}
}
}
break;
case 0x03: // File time
{
uint64_t flags=ReadRAR5VInt(handle);
if(flags&0x0002)
{
if(flags&0x0001)
{
uint32_t time=[handle readUInt32LE];
[dict setObject:[NSDate dateWithTimeIntervalSince1970:time]
forKey:XADLastModificationDateKey];
}
else
{
uint64_t time=[handle readUInt64LE];
[dict setObject:[NSDate XADDateWithWindowsFileTime:time]
forKey:XADLastModificationDateKey];
}
}
if(flags&0x0004)
{
if(flags&0x0001)
{
uint32_t time=[handle readUInt32LE];
[dict setObject:[NSDate dateWithTimeIntervalSince1970:time]
forKey:XADCreationDateKey];
}
else
{
uint64_t time=[handle readUInt64LE];
[dict setObject:[NSDate XADDateWithWindowsFileTime:time]
forKey:XADCreationDateKey];
}
}
if(flags&0x0008)
{
if(flags&0x0001)
{
uint32_t time=[handle readUInt32LE];
[dict setObject:[NSDate dateWithTimeIntervalSince1970:time]
forKey:XADLastAccessDateKey];
}
else
{
uint64_t time=[handle readUInt64LE];
[dict setObject:[NSDate XADDateWithWindowsFileTime:time]
forKey:XADLastAccessDateKey];
}
}
}
break;
case 0x04: // File version
{
/*uint64_t flags=*/ReadRAR5VInt(handle);
uint64_t version=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:version] forKey:@"RAR5FileVersion"];
}
break;
case 0x05: // Redirection
{
uint64_t type=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:type] forKey:@"RAR5RedirectionType"];
if(type==0x004)
[dict setObject:[NSNumber numberWithBool:YES] forKey:XADIsHardLinkKey];
uint64_t flags=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:flags] forKey:@"RAR5RedirectionFlags"];
uint64_t namelength=ReadRAR5VInt(handle);
NSData *namedata=[handle readDataOfLength:namelength];
[dict setObject:[self XADStringWithData:namedata encodingName:XADUTF8StringEncodingName]
forKey:XADLinkDestinationKey];
}
break;
case 0x06: // Unix owner
{
uint64_t flags=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:flags] forKey:@"RAR5RedirectionFlags"];
if(flags&0x0001)
{
uint64_t namelength=ReadRAR5VInt(handle);
NSData *namedata=[handle readDataOfLength:namelength];
[dict setObject:[self XADStringWithData:namedata]
forKey:XADPosixUserNameKey];
}
if(flags&0x0002)
{
uint64_t namelength=ReadRAR5VInt(handle);
NSData *namedata=[handle readDataOfLength:namelength];
[dict setObject:[self XADStringWithData:namedata]
forKey:XADPosixGroupNameKey];
}
if(flags&0x0004)
{
uint64_t num=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:num]
forKey:XADPosixUserKey];
}
if(flags&0x0008)
{
uint64_t num=ReadRAR5VInt(handle);
[dict setObject:[NSNumber numberWithUnsignedLongLong:num]
forKey:XADPosixUserKey];
}
}
break;
case 0x07: // Service data
break;
}
[handle seekToFileOffset:start+size];
if(start+size>=extraend) break;
}
}
return dict;
}
+(RAR5HeaderBlock)readMasterHeaderFromHandle:(CSHandle *)handle
{
RAR5HeaderBlock header;
RAR5Block block;
block.outerstart=0;
@try
{
CSHandle * signatureSearchingHandle = [handle subHandleToEndOfFileFrom:0];
NSData * data = [signatureSearchingHandle readDataOfLengthAtMost:RAR5MaximumSFXHeader];
off_t signatureLocation = [XADRAR5Parser signatureLocationInData:data];
if (signatureLocation == RAR5SignatureNotFound) {
[NSException raise:RAR5SignatureCannotBeFound format:@"Signature cannot be found %@",[self class]];
}
[handle skipBytes:signatureLocation];
[handle skipBytes:8];
if([handle atEndOfFile]) return ZeroHeaderBlock;
block.crc=[handle readUInt32LE];
block.headersize=ReadRAR5VInt(handle);
block.start=[handle offsetInFile];
block.type=ReadRAR5VInt(handle);
block.flags=ReadRAR5VInt(handle);
if(block.flags&0x0001) block.extrasize=ReadRAR5VInt(handle);
else block.extrasize=0;
header.archiveFlags=ReadRAR5VInt(handle);
if(block.flags&0x0002) block.datasize=ReadRAR5VInt(handle);
else block.datasize=0;
}
@catch(id e) { return ZeroHeaderBlock; }
// If first block wasn't main
if (block.type != RAR5HeaderTypeMain) {
return ZeroHeaderBlock;
}
block.fh=handle;
header.block = block;
return header;
}
-(RAR5Block)readBlockHeader
{
CSHandle *fh=[self handle];
if([fh atEndOfFile]) return ZeroBlock;
RAR5Block block;
block.outerstart=0;
if(headerkey)
{
NSData *iv=[fh readDataOfLength:16];
block.outerstart=[fh offsetInFile];
fh=[[[XADRARAESHandle alloc] initWithHandle:fh RAR5Key:headerkey IV:iv] autorelease];
}
block.fh=fh;
@try
{
block.crc=[fh readUInt32LE];
block.headersize=ReadRAR5VInt(fh);
block.start=[fh offsetInFile];
block.type=ReadRAR5VInt(fh);
block.flags=ReadRAR5VInt(fh);
if(block.flags&0x0001) block.extrasize=ReadRAR5VInt(fh);
else block.extrasize=0;
if(block.flags&0x0002) block.datasize=ReadRAR5VInt(fh);
else block.datasize=0;
}
@catch(id e) { return ZeroBlock; }
block.fh=fh;
//NSLog(@"headsize:%llu block:%llu flags:%llx extrasize:%llu datasize:%llu",block.headersize,block.type,block.flags,block.extrasize,block.datasize);
return block;
}
-(void)skipBlock:(RAR5Block)block
{
[[self handle] seekToFileOffset:[self endOfBlockHeader:block]+block.datasize];
}
-(off_t)endOfBlockHeader:(RAR5Block)block
{
if(block.outerstart)
{
return block.outerstart+((block.start+block.headersize+15)&~15);
}
else
{
return block.start+block.headersize;
}
}
-(NSData *)encryptionKeyForPassword:(NSString *)passwordstring salt:(NSData *)salt strength:(int)strength passwordCheck:(NSData *)check
{
return [[self keysForPassword:passwordstring salt:salt strength:strength passwordCheck:check] objectForKey:@"Key"];
}
-(NSData *)hashKeyForPassword:(NSString *)passwordstring salt:(NSData *)salt strength:(int)strength passwordCheck:(NSData *)check
{
return [[self keysForPassword:passwordstring salt:salt strength:strength passwordCheck:check] objectForKey:@"HashKey"];
}
-(NSDictionary *)keysForPassword:(NSString *)passwordstring salt:(NSData *)salt strength:(int)strength passwordCheck:(NSData *)check
{
NSArray *key=[NSArray arrayWithObjects:password,salt,[NSNumber numberWithInt:strength],nil];
NSDictionary *crypto=[cryptocache objectForKey:key];
if(!crypto)
{
NSData *passworddata=[passwordstring dataUsingEncoding:NSUTF8StringEncoding];
uint8_t DK1[32],DK2[32],DK3[32];
PBKDF2_3([passworddata bytes],[passworddata length],[salt bytes],[salt length],
DK1,DK2,DK3,32,1<<strength,16,16);
if(check && [check length]==8)
{
const uint8_t *checkbytes=[check bytes];
for(int i=0;i<8;i++)
{
if(checkbytes[i]!=(DK3[i]^DK3[i+8]^DK3[i+16]^DK3[i+24]))
[XADException raisePasswordException];
}
}
crypto=[NSDictionary dictionaryWithObjectsAndKeys:
[NSData dataWithBytes:DK1 length:32],@"Key",
[NSData dataWithBytes:DK2 length:32],@"HashKey",
//[NSData dataWithBytes:DK3 length:32],@"PasswordCheck",
nil];
[cryptocache setObject:crypto forKey:key];
}
return crypto;
}
-(CSHandle *)handleForEntryWithDictionary:(NSDictionary *)dict wantChecksum:(BOOL)checksum
{
// Give the caller some ahead notice if we will be using a password.
NSNumber *encryptnum=[dict objectForKey:XADIsEncryptedKey];
if(encryptnum && [encryptnum boolValue])
{
[self password];
if(![self hasPassword]) return nil;
}
CSHandle *handle;
if([[dict objectForKey:@"RAR5CompressionMethod"] intValue]==0)
{
handle=[self inputHandleWithDictionary:dict];
off_t length=[[dict objectForKey:XADSolidLengthKey] longLongValue];
if(length!=[handle fileSize]) handle=[handle nonCopiedSubHandleOfLength:length];
}
else
{
off_t length=[[dict objectForKey:XADSolidLengthKey] longLongValue];
// Avoid 0-length files because they make trouble in solid streams.
if(length==0) handle=[self zeroLengthHandleWithChecksum:YES];
else handle=[self subHandleFromSolidStreamForEntryWithDictionary:dict];
}
if(checksum)
{
NSNumber *crc=[dict objectForKey:@"RAR5CRC32"];
if(crc)
{
XADCRCHandle *crchandle=[XADCRCHandle IEEECRC32HandleWithHandle:handle length:[handle fileSize]
correctCRC:[crc unsignedIntValue] conditioned:YES];
NSNumber *checksumsencrypted=[dict objectForKey:@"RAR5ChecksumsAreEncrypted"];
if(checksumsencrypted && [checksumsencrypted boolValue])
{
NSNumber *strength=[dict objectForKey:@"RAR5EncryptionStrength"];
NSData *salt=[dict objectForKey:@"RAR5EncryptionSalt"];
NSData *passcheck=[dict objectForKey:@"RAR5EncryptionCheckData"];
NSData *key=[self hashKeyForPassword:[self password]
salt:salt strength:strength.intValue passwordCheck:passcheck];
[crchandle setCRCTransformationFunction:EncryptRAR5CRC32 context:key];
}
handle=crchandle;
}
// TODO: Add blake2sp
}
return handle;
}
-(CSHandle *)handleForSolidStreamWithObject:(id)obj wantChecksum:(BOOL)checksum
{
NSNumber *index=obj;
NSArray *stream=[solidstreams objectAtIndex:[index integerValue]];
return [[[XADRAR50Handle alloc] initWithRARParser:self files:stream] autorelease];
}
-(CSInputBuffer *)inputBufferWithDictionary:(NSDictionary *)dict
{
return CSInputBufferAlloc([self inputHandleWithDictionary:dict],16384);
}
-(CSHandle *)inputHandleWithDictionary:(NSDictionary *)dict
{
NSArray *parts=[dict objectForKey:@"RAR5InputParts"];
CSHandle *handle=[[[XADRARInputHandle alloc] initWithHandle:[self handle] parts:parts] autorelease];
NSNumber *encryptnum=[dict objectForKey:XADIsEncryptedKey];
if(encryptnum && [encryptnum boolValue])
{
NSNumber *strength=[dict objectForKey:@"RAR5EncryptionStrength"];
NSData *salt=[dict objectForKey:@"RAR5EncryptionSalt"];
NSData *iv=[dict objectForKey:@"RAR5EncryptionIV"];
NSData *passcheck=[dict objectForKey:@"RAR5EncryptionCheckData"];
NSData *key=[self encryptionKeyForPassword:[self password]
salt:salt strength:strength.intValue passwordCheck:passcheck];
return [[[XADRARAESHandle alloc] initWithHandle:handle
length:[handle fileSize] RAR5Key:key IV:iv] autorelease];
}
else
{
return handle;
}
}
-(NSString *)formatName
{
return @"RAR 5";
}
@end
@implementation XADEmbeddedRAR5Parser
-(NSString *)formatName
{
return @"Embedded RAR 5";
}
@end
static uint32_t EncryptRAR5CRC32(uint32_t crc,id context)
{
NSData *key=context;
uint8_t crcbytes[4];
CSSetUInt32LE(crcbytes,crc^0xffffffff);
uint8_t digest[HMAC_SHA256_DIGEST_LENGTH];
HMAC_SHA256_CTX hmac;
HMAC_SHA256_Init(&hmac);
HMAC_SHA256_UpdateKey(&hmac,[key bytes],[key length]);
HMAC_SHA256_EndKey(&hmac);
HMAC_SHA256_StartMessage(&hmac);
HMAC_SHA256_UpdateMessage(&hmac,crcbytes,4);
HMAC_SHA256_EndMessage(digest,&hmac);
HMAC_SHA256_Done(&hmac);
uint32_t newcrc=0;
for(int i=0;i<sizeof(digest);i++) newcrc^=digest[i]<<((i&3)*8);
return newcrc^0xffffffff;
}
@implementation XADRAR5Parser (Testing)
+(uint64_t)readRAR5VIntFrom:(CSHandle *)handle
{
return ReadRAR5VInt(handle);
}
@end
@implementation XADRAR5Parser (Multipart)
+(BOOL)isPartOfMultiVolume:(CSHandle *)handle
{
RAR5HeaderBlock header = [self readMasterHeaderFromHandle:handle];
if (IsZeroHeaderBlock(header)) {
return NO;
}
return (header.archiveFlags & RAR5ArchiveFlagsVolume);
}
@end