mirror of
https://github.com/MacPaw/XADMaster.git
synced 2025-08-29 03:23:48 +02:00
316 lines
7.4 KiB
Objective-C
316 lines
7.4 KiB
Objective-C
/*
|
|
* XADGzipParser.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 "XADGzipParser.h"
|
|
#import "CSZlibHandle.h"
|
|
#import "CRC.h"
|
|
#import "Progress.h"
|
|
#import "Scanning.h"
|
|
|
|
|
|
|
|
@implementation XADGzipParser
|
|
|
|
+(int)requiredHeaderSize { return 3; }
|
|
|
|
+(BOOL)recognizeFileWithHandle:(CSHandle *)handle firstBytes:(NSData *)data name:(NSString *)name;
|
|
{
|
|
const uint8_t *bytes=[data bytes];
|
|
int length=[data length];
|
|
|
|
if(length<3) return NO;
|
|
if(bytes[0]==0x1f)
|
|
{
|
|
if((bytes[1]==0x8b||bytes[1]==0x9e) && bytes[2]==8) return YES;
|
|
if(bytes[1]==0xa1) return YES; /* BSD-compress variant */
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
-(void)parse
|
|
{
|
|
CSHandle *handle=[self handle];
|
|
|
|
uint16_t headid=[handle readUInt16BE];
|
|
uint8_t method=[handle readUInt8];
|
|
uint8_t flags=[handle readUInt8];
|
|
uint32_t time=[handle readUInt32LE];
|
|
uint8_t extraflags=[handle readUInt8];
|
|
uint8_t os=[handle readUInt8];
|
|
|
|
if(method!=8) [XADException raiseIllegalDataException];
|
|
|
|
NSMutableData *filename=nil,*comment=nil;
|
|
|
|
if(headid!=0x1fa1)
|
|
{
|
|
if(flags&0x04) // FEXTRA: extra fields
|
|
{
|
|
uint16_t len=[handle readUInt16LE];
|
|
[handle skipBytes:len];
|
|
}
|
|
if(flags&0x08) // FNAME: filename
|
|
{
|
|
filename=[NSMutableData data];
|
|
uint8_t chr;
|
|
while((chr=[handle readUInt8])) [filename appendBytes:&chr length:1];
|
|
}
|
|
if(flags&0x10) // FCOMMENT: comment
|
|
{
|
|
comment=[NSMutableData data];
|
|
uint8_t chr;
|
|
while((chr=[handle readUInt8])) [comment appendBytes:&chr length:1];
|
|
}
|
|
if(flags&0x02) // FHCRC: header crc
|
|
{
|
|
[handle skipBytes:2];
|
|
}
|
|
}
|
|
|
|
NSString *name=[self name];
|
|
NSString *extension=[[name pathExtension] lowercaseString];
|
|
NSString *contentname;
|
|
if([extension isEqual:@"tgz"]) contentname=[[name stringByDeletingPathExtension] stringByAppendingPathExtension:@"tar"];
|
|
else if([extension isEqual:@"adz"]) contentname=[[name stringByDeletingPathExtension] stringByAppendingPathExtension:@"adf"];
|
|
else if([extension isEqual:@"cpgz"]) contentname=[[name stringByDeletingPathExtension] stringByAppendingPathExtension:@"cpio"];
|
|
else contentname=[name stringByDeletingPathExtension];
|
|
|
|
// TODO: set no filename flag
|
|
NSMutableDictionary *dict=[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[self XADPathWithUnseparatedString:contentname],XADFileNameKey,
|
|
[self XADStringWithString:@"Deflate"],XADCompressionNameKey,
|
|
[NSNumber numberWithUnsignedInt:extraflags],@"GzipExtraFlags",
|
|
[NSNumber numberWithUnsignedInt:os],@"GzipOS",
|
|
nil];
|
|
|
|
if(time) [dict setObject:[NSDate dateWithTimeIntervalSince1970:time] forKey:XADLastModificationDateKey];
|
|
|
|
if([contentname matchedByPattern:@"\\.(tar|cpio|pax|warc)$" options:REG_ICASE])
|
|
[dict setObject:[NSNumber numberWithBool:YES] forKey:XADIsArchiveKey];
|
|
|
|
off_t filesize=[handle fileSize];
|
|
if(filesize!=CSHandleMaxLength)
|
|
[dict setObject:[NSNumber numberWithLongLong:filesize] forKey:XADCompressedSizeKey];
|
|
|
|
if(filename)
|
|
[dict setObject:[self XADStringWithData:filename] forKey:@"GzipFilename"];
|
|
|
|
if(comment)
|
|
[dict setObject:[self XADStringWithData:comment] forKey:XADCommentKey];
|
|
|
|
[self addEntryWithDictionary:dict];
|
|
}
|
|
|
|
-(CSHandle *)handleForEntryWithDictionary:(NSDictionary *)dictionary wantChecksum:(BOOL)checksum
|
|
{
|
|
CSHandle *handle=[self handle];
|
|
[handle seekToFileOffset:0];
|
|
return [[[XADGzipHandle alloc] initWithHandle:handle] autorelease];
|
|
}
|
|
|
|
-(NSString *)formatName { return @"Gzip"; }
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation XADGzipSFXParser
|
|
|
|
+(int)requiredHeaderSize { return 5000; }
|
|
|
|
+(BOOL)recognizeFileWithHandle:(CSHandle *)handle firstBytes:(NSData *)data
|
|
name:(NSString *)name propertiesToAdd:(NSMutableDictionary *)props
|
|
{
|
|
const uint8_t *bytes=[data bytes];
|
|
int length=[data length];
|
|
|
|
if(length<5) return NO;
|
|
if(length>5000) length=5000;
|
|
|
|
if(bytes[0]!='#'||bytes[1]!='!') return NO;
|
|
|
|
for(int i=2;i<length-3;i++)
|
|
{
|
|
if(bytes[i]==0x1f&&(bytes[i+1]==0x8b||bytes[i+1]==0x9e)&&bytes[i+2]==8)
|
|
{
|
|
[props setObject:[NSNumber numberWithInt:i] forKey:@"GzipSFXOffset"];
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
-(void)parse
|
|
{
|
|
off_t offs=[[[self properties] objectForKey:@"GzipSFXOffset"] longLongValue];
|
|
[[self handle] seekToFileOffset:offs];
|
|
|
|
[super parse];
|
|
}
|
|
|
|
-(NSString *)formatName { return @"Gzip (Self-extracting)"; }
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
#define HeaderState 0
|
|
#define DataState 1
|
|
#define FooterState 2
|
|
#define EndState 10
|
|
|
|
@implementation XADGzipHandle
|
|
|
|
-(id)initWithHandle:(CSHandle *)handle
|
|
{
|
|
if(self=[super initWithParentHandle:handle])
|
|
{
|
|
startoffs=[parent offsetInFile];
|
|
currhandle=nil;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
-(void)dealloc
|
|
{
|
|
[currhandle release];
|
|
[super dealloc];
|
|
}
|
|
|
|
-(void)resetStream
|
|
{
|
|
[parent seekToFileOffset:startoffs];
|
|
state=HeaderState;
|
|
checksumscorrect=YES;
|
|
}
|
|
|
|
-(int)streamAtMost:(int)num toBuffer:(void *)buffer
|
|
{
|
|
int bytesread=0;
|
|
uint8_t *bytebuf=buffer;
|
|
|
|
while(bytesread<num && state!=EndState) switch(state)
|
|
{
|
|
case HeaderState:
|
|
{
|
|
uint16_t headid=[parent readUInt16BE];
|
|
uint8_t method=[parent readUInt8];
|
|
uint8_t flags=[parent readUInt8];
|
|
/*uint32_t time=[parent readUInt32LE];
|
|
uint8_t extraflags=[parent readUInt8];
|
|
uint8_t os=[parent readUInt8];*/
|
|
[parent skipBytes:6];
|
|
|
|
if(method!=8) [XADException raiseIllegalDataException];
|
|
|
|
if(headid!=0x1fa1)
|
|
{
|
|
if(flags&0x04) // FEXTRA: extra fields
|
|
{
|
|
uint16_t len=[parent readUInt16LE];
|
|
[parent skipBytes:len];
|
|
}
|
|
if(flags&0x08) // FNAME: filename
|
|
{
|
|
while([parent readUInt8]);
|
|
}
|
|
if(flags&0x10) // FCOMMENT: comment
|
|
{
|
|
while([parent readUInt8]);
|
|
}
|
|
if(flags&0x02) // FHCRC: header crc
|
|
{
|
|
[parent skipBytes:2];
|
|
}
|
|
}
|
|
|
|
CSZlibHandle *zh=[CSZlibHandle deflateHandleWithHandle:parent];
|
|
[zh setSeekBackAtEOF:YES];
|
|
|
|
[currhandle release];
|
|
currhandle=[zh retain];
|
|
|
|
crc=0xffffffff;
|
|
|
|
state=DataState;
|
|
}
|
|
break;
|
|
|
|
case DataState:
|
|
{
|
|
int actual=[currhandle readAtMost:num-bytesread toBuffer:&bytebuf[bytesread]];
|
|
crc=XADCalculateCRCFast(crc,&bytebuf[bytesread],actual,XADCRCTable_sliced16_edb88320);
|
|
|
|
bytesread+=actual;
|
|
|
|
if([currhandle atEndOfFile]) state=FooterState;
|
|
}
|
|
break;
|
|
|
|
case FooterState:
|
|
{
|
|
@try
|
|
{
|
|
uint32_t correctcrc=[parent readUInt32LE];
|
|
/*uint32_t len=[parent readUInt32LE];*/
|
|
[parent skipBytes:4];
|
|
if((crc^0xffffffff)!=correctcrc) checksumscorrect=NO;
|
|
}
|
|
@catch(id e)
|
|
{
|
|
checksumscorrect=NO;
|
|
state=EndState;
|
|
break;
|
|
}
|
|
|
|
if([parent atEndOfFile]) state=EndState;
|
|
else state=HeaderState;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if(state==EndState)
|
|
{
|
|
[self endStream];
|
|
[currhandle release];
|
|
currhandle=nil;
|
|
}
|
|
|
|
return bytesread;
|
|
}
|
|
|
|
-(BOOL)hasChecksum
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
-(BOOL)isChecksumCorrect
|
|
{
|
|
if(currhandle) return NO;
|
|
return checksumscorrect;
|
|
}
|
|
|
|
-(double)estimatedProgress { return [parent estimatedProgress]; } // TODO: better estimation using buffer?
|
|
|
|
@end
|
|
|