mirror of
https://github.com/MacPaw/XADMaster.git
synced 2025-08-29 19:43:47 +02:00
309 lines
9.3 KiB
Objective-C
309 lines
9.3 KiB
Objective-C
/*
|
|
* XADARJParser.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 "XADARJParser.h"
|
|
#import "XADARJFastestHandle.h"
|
|
#import "XADLZHStaticHandle.h"
|
|
#import "XADXORHandle.h"
|
|
#import "XADCRCHandle.h"
|
|
#import "NSDateXAD.h"
|
|
#import "CRC.h"
|
|
|
|
// TODO: Multi-volume support
|
|
|
|
static int StringLength(const char *str,const char *end);
|
|
static NSData *ReadNullTerminatedString(CSHandle *fh);
|
|
|
|
@implementation XADARJParser
|
|
|
|
+(int)requiredHeaderSize { return 0x8000; }
|
|
|
|
+(BOOL)recognizeFileWithHandle:(CSHandle *)handle firstBytes:(NSData *)data name:(NSString *)name
|
|
{
|
|
const uint8_t *bytes=[data bytes];
|
|
int length=[data length];
|
|
|
|
if(length<40) return NO;
|
|
|
|
for(int i=0;i<=length-4;i++)
|
|
{
|
|
if(bytes[i]==0x60&&bytes[i+1]==0xea)
|
|
{
|
|
int size=CSUInt16LE(&bytes[i+2]);
|
|
if(size>=32 && size<=2600 && i+4+size+4<=length)
|
|
{
|
|
uint32_t headcrc=XADCalculateCRC(0xffffffff,&bytes[i+4],size,XADCRCTable_edb88320);
|
|
uint32_t storedcrc=CSUInt32LE(&bytes[i+4+size]);
|
|
if(storedcrc==~headcrc) return YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
-(void)parse
|
|
{
|
|
CSHandle *fh=[self handle];
|
|
|
|
int headersize;
|
|
NSData *header;
|
|
for(;;)
|
|
{
|
|
int byte;
|
|
do byte=[fh readUInt8];
|
|
while(byte==0x60);
|
|
|
|
if(byte==0xea)
|
|
{
|
|
off_t pos=[fh offsetInFile];
|
|
|
|
headersize=[fh readUInt16LE];
|
|
if(headersize>=32 && headersize<=2600)
|
|
{
|
|
header=[fh readDataOfLength:headersize];
|
|
uint32_t crc=[fh readUInt32LE];
|
|
if(XADCalculateCRC(0xffffffff,[header bytes],headersize,XADCRCTable_edb88320)==~crc)
|
|
break;
|
|
}
|
|
|
|
[fh seekToFileOffset:pos];
|
|
}
|
|
}
|
|
|
|
const uint8_t *headerbytes=[header bytes];
|
|
|
|
int firstsize=headerbytes[0];
|
|
//int version=headerbytes[1];
|
|
//int minversion=headerbytes[2];
|
|
int os=headerbytes[3];
|
|
//int archiveflags=headerbytes[4];
|
|
//int securityversion=headerbytes[5];
|
|
int filetype=headerbytes[6];
|
|
uint32_t archivecreataion=CSUInt32LE(&headerbytes[8]);
|
|
uint32_t archivemodification=CSUInt32LE(&headerbytes[12]);
|
|
//uint32_t archivesize=CSUInt32LE(&headerbytes[16]);
|
|
//uint32_t securityoffs=CSUInt32LE(&headerbytes[20]);
|
|
//int filespecpos=CSUInt16LE(&headerbytes[24]);
|
|
//int securitylength=CSUInt16LE(&headerbytes[26]);
|
|
|
|
if(filetype!=2) [XADException raiseIllegalDataException];
|
|
|
|
const char *filename=(const char *)&headerbytes[firstsize];
|
|
int filenamelen=StringLength(filename,(const char *)&headerbytes[headersize]);
|
|
|
|
const char *comment=(const char *)&headerbytes[firstsize+filenamelen+1];
|
|
int commentlen=StringLength(comment,(const char *)&headerbytes[headersize]);
|
|
|
|
if(os==2) // Unix dates
|
|
{
|
|
[properties setObject:[NSDate dateWithTimeIntervalSince1970:archivecreataion] forKey:XADCreationDateKey];
|
|
[properties setObject:[NSDate dateWithTimeIntervalSince1970:archivemodification] forKey:XADLastModificationDateKey];
|
|
}
|
|
else // MS-DOS/Windows dates
|
|
{
|
|
[properties setObject:[NSDate XADDateWithMSDOSDateTime:archivecreataion] forKey:XADCreationDateKey];
|
|
[properties setObject:[NSDate XADDateWithMSDOSDateTime:archivemodification] forKey:XADLastModificationDateKey];
|
|
}
|
|
|
|
if(filenamelen) [properties setObject:[self XADStringWithBytes:filename length:filenamelen]
|
|
forKey:@"ARJOriginalArchiveName"];
|
|
if(commentlen) [properties setObject:[self XADStringWithBytes:comment length:commentlen]
|
|
forKey:XADCommentKey];
|
|
|
|
int extlen=[fh readUInt16LE];
|
|
if(extlen) [fh skipBytes:extlen+4];
|
|
|
|
while([self shouldKeepParsing])
|
|
{
|
|
if([fh readUInt8]!=0x60||[fh readUInt8]!=0xea) [XADException raiseIllegalDataException];
|
|
|
|
int headersize=[fh readUInt16LE];
|
|
if(headersize==0) break;
|
|
if(headersize<32||headersize>2600) [XADException raiseIllegalDataException];
|
|
|
|
int firstsize=[fh readUInt8];
|
|
int version=[fh readUInt8];
|
|
int minversion=[fh readUInt8];
|
|
int os=[fh readUInt8];
|
|
int flags=[fh readUInt8];
|
|
int method=[fh readUInt8];
|
|
int filetype=[fh readUInt8];
|
|
int passwordmod=[fh readUInt8];
|
|
uint32_t modification=[fh readUInt32LE];
|
|
uint32_t compsize=[fh readUInt32LE];
|
|
uint32_t size=[fh readUInt32LE];
|
|
uint32_t crc=[fh readUInt32LE];
|
|
/*int filespecoffs=*/[fh readUInt16LE];
|
|
int accessmode=[fh readUInt16LE];
|
|
[fh skipBytes:firstsize-28];
|
|
|
|
NSData *filename=ReadNullTerminatedString(fh);
|
|
NSData *comment=ReadNullTerminatedString(fh);
|
|
|
|
/*uint32_t headcrc=*/[fh readUInt32LE];
|
|
|
|
int extlen=[fh readUInt16LE];
|
|
if(extlen) [fh skipBytes:extlen+4];
|
|
|
|
const char *separator;
|
|
if(flags&0x10) separator=XADUnixPathSeparator;
|
|
else separator=XADWindowsPathSeparator;
|
|
|
|
off_t pos=[fh offsetInFile];
|
|
|
|
NSDate *modificationdate;
|
|
if(os==2) modificationdate=[NSDate dateWithTimeIntervalSince1970:modification];
|
|
else modificationdate=[NSDate XADDateWithMSDOSDateTime:modification];
|
|
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[self XADPathWithData:filename separators:separator],XADFileNameKey,
|
|
[NSNumber numberWithLongLong:pos],XADDataOffsetKey,
|
|
[NSNumber numberWithLongLong:compsize],XADDataLengthKey,
|
|
[NSNumber numberWithLongLong:compsize],XADCompressedSizeKey,
|
|
[NSNumber numberWithLongLong:size],XADFileSizeKey,
|
|
modificationdate,XADLastModificationDateKey,
|
|
[NSNumber numberWithInt:accessmode],XADDOSFileAttributesKey,
|
|
[NSNumber numberWithInt:version],@"ARJVersion",
|
|
[NSNumber numberWithInt:minversion],@"ARJMinimumVersion",
|
|
[NSNumber numberWithInt:os],@"ARJOS",
|
|
[NSNumber numberWithInt:flags],@"ARJFlags",
|
|
[NSNumber numberWithInt:method],@"ARJMethod",
|
|
[NSNumber numberWithInt:filetype],@"ARJFileType",
|
|
[NSNumber numberWithInt:crc],@"ARJCRC32",
|
|
nil];
|
|
|
|
if(filetype==3) [dict setObject:[NSNumber numberWithBool:YES] forKey:XADIsDirectoryKey];
|
|
|
|
if(flags&0x01)
|
|
{
|
|
[dict setObject:[NSNumber numberWithBool:YES] forKey:XADIsEncryptedKey];
|
|
[dict setObject:[NSNumber numberWithInt:passwordmod] forKey:@"ARJPasswordModifier"];
|
|
}
|
|
|
|
NSString *osname=nil;
|
|
switch(os)
|
|
{
|
|
case 0: osname=@"MS-DOS"; break;
|
|
case 1: osname=@"PRIMOS"; break;
|
|
case 2: osname=@"Unix"; break;
|
|
case 3: osname=@"Amiga"; break;
|
|
case 4: osname=@"Mac OS"; break;
|
|
case 5: osname=@"OS/2"; break;
|
|
case 6: osname=@"Apple GS"; break;
|
|
case 7: osname=@"Atari ST"; break;
|
|
case 8: osname=@"NeXT"; break;
|
|
case 9: osname=@"VAX VMS"; break;
|
|
case 10: osname=@"Windows 95"; break;
|
|
case 11: osname=@"Win32"; break;
|
|
}
|
|
if(osname) [dict setObject:[self XADStringWithString:osname] forKey:@"ARJOSName"];
|
|
|
|
NSString *methodname=nil;
|
|
switch(method)
|
|
{
|
|
case 0: methodname=@"None"; break;
|
|
case 1: methodname=@"Most"; break;
|
|
case 2: methodname=@"Medium"; break;
|
|
case 3: methodname=@"Fast"; break;
|
|
case 4: methodname=@"Fastest"; break;
|
|
}
|
|
if(methodname) [dict setObject:[self XADStringWithString:methodname] forKey:XADCompressionNameKey];
|
|
|
|
if([comment length]) [dict setObject:[self XADStringWithData:comment] forKey:XADCommentKey];
|
|
|
|
[self addEntryWithDictionary:dict];
|
|
|
|
[fh seekToFileOffset:pos+compsize];
|
|
}
|
|
}
|
|
|
|
|
|
-(CSHandle *)handleForEntryWithDictionary:(NSDictionary *)dict wantChecksum:(BOOL)checksum
|
|
{
|
|
CSHandle *handle=[self handleAtDataOffsetForDictionary:dict];
|
|
off_t size=[[dict objectForKey:XADFileSizeKey] longLongValue];
|
|
int method=[[dict objectForKey:@"ARJMethod"] intValue];
|
|
uint32_t crc=[[dict objectForKey:@"ARJCRC32"] unsignedLongValue];
|
|
NSNumber *crypto=[dict objectForKey:XADIsEncryptedKey];
|
|
|
|
if(crypto&&[crypto boolValue])
|
|
{
|
|
NSMutableData *passdata=[NSMutableData dataWithData:[self encodedPassword]];
|
|
uint8_t *passbytes=[passdata mutableBytes];
|
|
int passlength=[passdata length];
|
|
int mod=[[dict objectForKey:@"ARJPasswordModifier"] intValue];
|
|
|
|
for(int i=0;i<passlength;i++) passbytes[i]+=mod;
|
|
|
|
handle=[[[XADXORHandle alloc] initWithHandle:handle password:passdata] autorelease];
|
|
|
|
// TODO: Handle GOST-40 and GOST-256
|
|
}
|
|
|
|
switch(method)
|
|
{
|
|
case 0: // No compression
|
|
break;
|
|
|
|
case 1: // LZH compression
|
|
case 2:
|
|
case 3:
|
|
handle=[[[XADLZHStaticHandle alloc] initWithHandle:handle length:size windowBits:15] autorelease];
|
|
break;
|
|
|
|
case 4: // Fast compression
|
|
handle=[[[XADARJFastestHandle alloc] initWithHandle:handle length:size] autorelease];
|
|
break;
|
|
|
|
default:
|
|
[self reportInterestingFileWithReason:@"Unsupported compression method %d",method];
|
|
return nil;
|
|
}
|
|
|
|
if(checksum) handle=[XADCRCHandle IEEECRC32HandleWithHandle:handle length:size correctCRC:crc conditioned:YES];
|
|
|
|
return handle;
|
|
}
|
|
|
|
-(NSString *)formatName { return @"ARJ"; }
|
|
|
|
@end
|
|
|
|
static int StringLength(const char *str,const char *end)
|
|
{
|
|
int len=0;
|
|
while(str+len<end && str[len]) len++;
|
|
return len;
|
|
}
|
|
|
|
static NSData *ReadNullTerminatedString(CSHandle *fh)
|
|
{
|
|
NSMutableData *data=[NSMutableData data];
|
|
|
|
for(;;)
|
|
{
|
|
uint8_t byte=[fh readUInt8];
|
|
if(!byte) break;
|
|
[data appendBytes:&byte length:1];
|
|
}
|
|
|
|
return data;
|
|
}
|