XADMaster/XADArchive.m

1239 lines
35 KiB
Objective-C

/*
* XADArchive.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
*/
#define XAD_NO_DEPRECATED
#import "XADArchive.h"
#import "CSMemoryHandle.h"
#import "CSHandle.h"
#import "Progress.h"
#import "NSDateXAD.h"
#import <sys/stat.h>
#import <sys/time.h>
NSString *XADResourceDataKey=@"XADResourceData";
NSString *XADFinderFlags=@"XADFinderFlags";
@implementation XADArchive
+(XADArchive *)archiveForFile:(NSString *)filename
{
return [[[XADArchive alloc] initWithFile:filename] autorelease];
}
+(XADArchive *)recursiveArchiveForFile:(NSString *)filename
{
XADArchive *archive=[self archiveForFile:filename];
while([archive numberOfEntries]==1)
{
XADArchive *subarchive=[[XADArchive alloc] initWithArchive:archive entry:0];
if(subarchive) archive=[subarchive autorelease];
else
{
[subarchive release];
break;
}
}
return archive;
}
+(NSArray *)volumesForFile:(NSString *)filename // deprecated
{
return [NSArray arrayWithObject:filename];
}
-(id)init
{
if((self=[super init]))
{
parser=nil;
unarchiver=nil;
delegate=nil;
lasterror=XADNoError;
immediatedestination=nil;
immediatefailed=NO;
immediatesize=0;
parentarchive=nil;
dataentries=[[NSMutableArray array] retain];
resourceentries=[[NSMutableArray array] retain];
namedict=nil;
}
return self;
}
-(id)initWithFile:(NSString *)file { return [self initWithFile:file delegate:nil error:NULL]; }
-(id)initWithFile:(NSString *)file error:(XADError *)error { return [self initWithFile:file delegate:nil error:error]; }
-(id)initWithFile:(NSString *)file delegate:(id)del error:(XADError *)error
{
if((self=[self init]))
{
delegate=del;
parser=[[XADArchiveParser archiveParserForPath:file] retain];
if(parser)
{
if([self _parseWithErrorPointer:error]) return self;
}
else if(error) *error=XADDataFormatError;
[self release];
}
return nil;
}
-(id)initWithData:(NSData *)data { return [self initWithData:data delegate:nil error:NULL]; }
-(id)initWithData:(NSData *)data error:(XADError *)error { return [self initWithData:data delegate:nil error:error]; }
-(id)initWithData:(NSData *)data delegate:(id)del error:(XADError *)error
{
if((self=[self init]))
{
delegate=del;
parser=[[XADArchiveParser archiveParserForHandle:[CSMemoryHandle memoryHandleForReadingData:data] name:@""] retain];
if(parser)
{
if([self _parseWithErrorPointer:error]) return self;
}
else if(error) *error=XADDataFormatError;
[self release];
}
return nil;
}
-(id)initWithArchive:(XADArchive *)otherarchive entry:(int)n { return [self initWithArchive:otherarchive entry:n delegate:nil error:NULL]; }
-(id)initWithArchive:(XADArchive *)otherarchive entry:(int)n error:(XADError *)error { return [self initWithArchive:otherarchive entry:n delegate:nil error:error]; }
-(id)initWithArchive:(XADArchive *)otherarchive entry:(int)n delegate:(id)del error:(XADError *)error
{
if((self=[self init]))
{
parentarchive=[otherarchive retain];
delegate=del;
CSHandle *handle=[otherarchive handleForEntry:n error:error];
if(handle)
{
parser=[[XADArchiveParser archiveParserForHandle:handle name:[otherarchive nameOfEntry:n]] retain];
if(parser)
{
if([self _parseWithErrorPointer:error]) return self;
}
else if(error) *error=XADDataFormatError;
}
[self release];
}
return nil;
}
-(id)initWithArchive:(XADArchive *)otherarchive entry:(int)n
immediateExtractionTo:(NSString *)destination error:(XADError *)error
{
return [self initWithArchive:otherarchive entry:n immediateExtractionTo:destination
subArchives:NO error:error];
}
-(id)initWithArchive:(XADArchive *)otherarchive entry:(int)n
immediateExtractionTo:(NSString *)destination subArchives:(BOOL)sub error:(XADError *)error
{
if((self=[self init]))
{
parentarchive=[otherarchive retain];
immediatedestination=destination;
immediatesubarchives=sub;
delegate=otherarchive;
immediatesize=[otherarchive representativeSizeOfEntry:n];
parser=[[XADArchiveParser archiveParserForEntryWithDictionary:
[otherarchive dataForkParserDictionaryForEntry:n]
archiveParser:otherarchive->parser wantChecksum:YES error:error] retain];
if(parser)
{
if([self _parseWithErrorPointer:error])
{
if(!immediatefailed)
{
XADError checksumerror=[parser testChecksumWithoutExceptions];
if(checksumerror)
{
lasterror=checksumerror;
if(error) *error=checksumerror;
immediatefailed=YES;
}
}
[self updateAttributesForDeferredDirectories];
immediatedestination=nil;
return self;
}
}
else if(error) *error=XADSubArchiveError;
[self release];
}
return nil;
}
-(void)dealloc
{
[parser release];
[unarchiver release];
[dataentries release];
[resourceentries release];
[namedict release];
[parentarchive release];
[super dealloc];
}
-(BOOL)_parseWithErrorPointer:(XADError *)error
{
unarchiver=[[XADUnarchiver unarchiverForArchiveParser:parser] retain];
[parser setDelegate:self];
[unarchiver setDelegate:self];
namedict=[[NSMutableDictionary dictionary] retain];
XADError parseerror=[parser parseWithoutExceptions];
if(parseerror)
{
lasterror=parseerror;
if(error) *error=parseerror;
}
if(immediatefailed&&error) *error=lasterror;
[namedict release];
namedict=nil;
return lasterror==XADNoError||[dataentries count]!=0;
}
-(void)archiveParser:(XADArchiveParser *)parser foundEntryWithDictionary:(NSDictionary *)dict
{
if(immediatefailed) return; // ignore anything after a failure
NSNumber *resnum=[dict objectForKey:XADIsResourceForkKey];
BOOL isres=resnum&&[resnum boolValue];
XADPath *name=[dict objectForKey:XADFileNameKey];
NSNumber *index=[namedict objectForKey:name];
if(index) // Try to update an existing entry
{
int n=[index intValue];
if(isres) // Adding a resource fork to an earlier data fork
{
if([resourceentries objectAtIndex:n]==[NSNull null])
{
[resourceentries replaceObjectAtIndex:n withObject:dict];
if(immediatedestination)
{
if(![self extractEntry:n to:immediatedestination
deferDirectories:YES dataFork:NO resourceFork:YES])
immediatefailed=YES;
}
return;
}
}
else // Adding a data fork to an earlier resource fork
{
if([dataentries objectAtIndex:n]==[NSNull null])
{
[dataentries replaceObjectAtIndex:n withObject:dict];
if(immediatedestination)
{
if(immediatesubarchives&&[self entryIsArchive:n])
{
// Try to extract as archive, if the format is unknown, extract as regular file
BOOL res;
@try { res=[self extractArchiveEntry:n to:immediatedestination]; }
@catch(id e) { res=NO; }
if(!res&&lasterror==XADDataFormatError)
{
if(![self extractEntry:n to:immediatedestination
deferDirectories:YES dataFork:YES resourceFork:NO])
immediatefailed=YES;
}
else immediatefailed=YES;
}
else
{
if(![self extractEntry:n to:immediatedestination
deferDirectories:YES dataFork:YES resourceFork:NO])
immediatefailed=YES;
}
}
return;
}
}
}
// Create a new entry instead
if(isres)
{
[dataentries addObject:[NSNull null]];
[resourceentries addObject:dict];
}
else
{
[dataentries addObject:dict];
[resourceentries addObject:[NSNull null]];
}
[namedict setObject:[NSNumber numberWithInt:[dataentries count]-1] forKey:name];
if(immediatedestination)
{
int n=[dataentries count]-1;
if(immediatesubarchives&&[self entryIsArchive:n])
{
// Try to extract as archive, if the format is unknown, extract as regular file
BOOL res;
@try { res=[self extractArchiveEntry:n to:immediatedestination]; }
@catch(id e) { res=NO; }
if(!res)
{
if(lasterror==XADDataFormatError)
{
if(![self extractEntry:n to:immediatedestination
deferDirectories:YES dataFork:YES resourceFork:YES])
immediatefailed=YES;
}
else immediatefailed=YES;
}
}
else
{
if(![self extractEntry:n to:immediatedestination
deferDirectories:YES dataFork:YES resourceFork:YES])
immediatefailed=YES;
}
}
}
-(BOOL)archiveParsingShouldStop:(XADArchiveParser *)parser
{
return immediatefailed;
}
-(void)archiveParserNeedsPassword:(XADArchiveParser *)parser
{
[delegate archiveNeedsPassword:self];
}
-(NSString *)filename
{
return [parser filename];
}
-(NSArray *)allFilenames
{
return [parser allFilenames];
}
-(NSString *)formatName
{
if(parentarchive) return [NSString stringWithFormat:@"%@ in %@",[parser formatName],[parentarchive formatName]];
else return [parser formatName];
}
-(BOOL)isEncrypted { return [parser isEncrypted]; }
-(BOOL)isSolid
{
NSNumber *issolid=[[parser properties] objectForKey:XADIsSolidKey];
if(!issolid) return NO;
return [issolid boolValue];
}
-(BOOL)isCorrupted
{
NSNumber *iscorrupted=[[parser properties] objectForKey:XADIsCorruptedKey];
if(!iscorrupted) return NO;
return [iscorrupted boolValue];
}
-(int)numberOfEntries { return [dataentries count]; }
-(BOOL)immediateExtractionFailed { return immediatefailed; }
-(NSString *)commonTopDirectory
{
NSString *firstname=[self nameOfEntry:0];
NSRange slash=[firstname rangeOfString:@"/"];
NSString *directory;
if(slash.location!=NSNotFound) directory=[firstname substringToIndex:slash.location];
else if([self entryIsDirectory:0]) directory=firstname;
else return nil;
NSString *dirprefix=[directory stringByAppendingString:@"/"];
int numentries=[self numberOfEntries];
for(int i=1;i<numentries;i++)
if(![[self nameOfEntry:i] hasPrefix:dirprefix]) return nil;
return directory;
}
-(NSString *)comment
{
return [[parser properties] objectForKey:XADCommentKey];
}
-(void)setDelegate:(id)newdelegate { delegate=newdelegate; }
-(id)delegate { return delegate; }
-(NSString *)password { return [parser password]; }
-(void)setPassword:(NSString *)newpassword { [parser setPassword:newpassword]; }
-(NSStringEncoding)nameEncoding { return [[parser stringSource] encoding]; }
-(void)setNameEncoding:(NSStringEncoding)encoding { [[parser stringSource] setFixedEncoding:encoding]; }
-(XADError)lastError { return lasterror; }
-(void)clearLastError { lasterror=XADNoError; }
-(NSString *)describeLastError { return [XADException describeXADError:lasterror]; }
-(NSString *)describeError:(XADError)error { return [XADException describeXADError:error]; }
-(NSString *)description
{
return [NSString stringWithFormat:@"XADArchive: %@ (%@, %d entries)",[self filename],[self formatName],[self numberOfEntries]];
}
-(NSDictionary *)dataForkParserDictionaryForEntry:(int)n
{
id obj=[dataentries objectAtIndex:n];
if(obj==[NSNull null]) return nil;
else return obj;
}
-(NSDictionary *)resourceForkParserDictionaryForEntry:(int)n
{
id obj=[resourceentries objectAtIndex:n];
if(obj==[NSNull null]) return nil;
else return obj;
}
-(NSDictionary *)combinedParserDictionaryForEntry:(int)n
{
NSDictionary *data=[dataentries objectAtIndex:n];
NSDictionary *resource=[resourceentries objectAtIndex:n];
if((id)data==[NSNull null]) return resource;
if((id)resource==[NSNull null]) return data;
NSMutableDictionary *new=[NSMutableDictionary dictionaryWithDictionary:data];
id obj;
obj=[resource objectForKey:XADFileTypeKey];
if(obj) [new setObject:obj forKey:XADFileTypeKey];
obj=[resource objectForKey:XADFileCreatorKey];
if(obj) [new setObject:obj forKey:XADFileCreatorKey];
obj=[resource objectForKey:XADFinderFlagsKey];
if(obj) [new setObject:obj forKey:XADFinderFlagsKey];
obj=[resource objectForKey:XADFinderInfoKey];
if(obj) [new setObject:obj forKey:XADFinderInfoKey];
return new;
}
-(NSString *)nameOfEntry:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
if(!dict) dict=[self resourceForkParserDictionaryForEntry:n];
XADPath *xadname=[dict objectForKey:XADFileNameKey];
if(!xadname) return nil;
if(![xadname encodingIsKnown]&&delegate)
{
NSStringEncoding encoding=[delegate archive:self encodingForData:[xadname data]
guess:[xadname encoding] confidence:[xadname confidence]];
return [xadname sanitizedPathStringWithEncoding:encoding];
}
else
{
return [xadname sanitizedPathString];
}
}
-(BOOL)entryHasSize:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
return [dict objectForKey:XADFileSizeKey]?YES:NO;
}
-(off_t)uncompressedSizeOfEntry:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
if(!dict) return 0; // Special case for resource forks without data forks
NSNumber *size=[dict objectForKey:XADFileSizeKey];
if(!size) return CSHandleMaxLength;
return [size longLongValue];
}
-(off_t)compressedSizeOfEntry:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
if(!dict) return 0; // Special case for resource forks without data forks
NSNumber *size=[dict objectForKey:XADCompressedSizeKey];
if(!size) return CSHandleMaxLength;
return [size longLongValue];
}
-(off_t)representativeSizeOfEntry:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
if(!dict) return 0; // Special case for resource forks without data forks
NSNumber *size=[dict objectForKey:XADFileSizeKey];
if(!size) size=[dict objectForKey:XADCompressedSizeKey];
if(!size) return 1000;
return [size longLongValue];
}
-(BOOL)entryIsDirectory:(int)n
{
NSDictionary *dict=[self combinedParserDictionaryForEntry:n];
NSNumber *isdir=[dict objectForKey:XADIsDirectoryKey];
return isdir&&[isdir boolValue];
}
-(BOOL)entryIsLink:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
NSNumber *islink=[dict objectForKey:XADIsLinkKey];
return islink&&[islink boolValue];
}
-(BOOL)entryIsEncrypted:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
NSNumber *isenc=[dict objectForKey:XADIsEncryptedKey];
return isenc&&[isenc boolValue];
}
-(BOOL)entryIsArchive:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
NSNumber *isarc=[dict objectForKey:XADIsArchiveKey];
return isarc&&[isarc boolValue];
}
-(BOOL)entryHasResourceFork:(int)n
{
NSDictionary *resdict=[self resourceForkParserDictionaryForEntry:n];
if(!resdict) return NO;
NSNumber *num=[resdict objectForKey:XADFileSizeKey];
if(!num) return NO;
return [num intValue]!=0;
}
-(NSString *)commentForEntry:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n]; // TODO: combined or data?
return [dict objectForKey:XADCommentKey];
}
-(NSDictionary *)attributesOfEntry:(int)n { return [self attributesOfEntry:n withResourceFork:NO]; }
-(NSDictionary *)attributesOfEntry:(int)n withResourceFork:(BOOL)resfork
{
NSDictionary *dict=[self combinedParserDictionaryForEntry:n];
NSMutableDictionary *attrs=[NSMutableDictionary dictionary];
NSDate *creation=[dict objectForKey:XADCreationDateKey];
NSDate *modification=[dict objectForKey:XADLastModificationDateKey];
if(modification) [attrs setObject:modification forKey:NSFileModificationDate];
if(creation) [attrs setObject:creation forKey:NSFileCreationDate];
NSNumber *type=[dict objectForKey:XADFileTypeKey];
if(type) [attrs setObject:type forKey:NSFileHFSTypeCode];
NSNumber *creator=[dict objectForKey:XADFileCreatorKey];
if(creator) [attrs setObject:creator forKey:NSFileHFSCreatorCode];
NSNumber *flags=[dict objectForKey:XADFinderFlagsKey];
if(flags) [attrs setObject:flags forKey:XADFinderFlagsKey];
NSNumber *perm=[dict objectForKey:XADPosixPermissionsKey];
if(perm) [attrs setObject:perm forKey:NSFilePosixPermissions];
XADString *user=[dict objectForKey:XADPosixUserNameKey];
if(user)
{
NSString *username=[user string];
if(username) [attrs setObject:username forKey:NSFileOwnerAccountName];
}
XADString *group=[dict objectForKey:XADPosixGroupNameKey];
if(group)
{
NSString *groupname=[group string];
if(groupname) [attrs setObject:groupname forKey:NSFileGroupOwnerAccountName];
}
if(resfork)
{
NSDictionary *resdict=[self resourceForkParserDictionaryForEntry:n];
if(resdict)
{
for(;;)
{
@try
{
CSHandle *handle=[parser handleForEntryWithDictionary:resdict wantChecksum:YES];
if(!handle) [XADException raiseDecrunchException];
NSData *forkdata=[handle remainingFileContents];
if([handle hasChecksum]&&![handle isChecksumCorrect]) [XADException raiseChecksumException];
[attrs setObject:forkdata forKey:XADResourceDataKey];
break;
}
@catch(id e)
{
lasterror=[XADException parseException:e];
XADAction action=[delegate archive:self extractionOfResourceForkForEntryDidFail:n error:lasterror];
if(action==XADSkipAction) break;
else if(action!=XADRetryAction) return nil;
}
}
}
}
return [NSDictionary dictionaryWithDictionary:attrs];
}
-(CSHandle *)handleForEntry:(int)n
{
return [self handleForEntry:n error:NULL];
}
-(CSHandle *)handleForEntry:(int)n error:(XADError *)error
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
if(!dict) return [CSMemoryHandle memoryHandleForReadingData:[NSData data]]; // Special case for files with only a resource fork
@try
{ return [parser handleForEntryWithDictionary:dict wantChecksum:YES]; }
@catch(id e)
{
lasterror=[XADException parseException:e];
if(error) *error=lasterror;
}
return nil;
}
-(CSHandle *)resourceHandleForEntry:(int)n
{
return [self resourceHandleForEntry:n error:NULL];
}
-(CSHandle *)resourceHandleForEntry:(int)n error:(XADError *)error
{
NSDictionary *resdict=[self resourceForkParserDictionaryForEntry:n];
if(!resdict) return nil;
NSNumber *isdir=[resdict objectForKey:XADIsDirectoryKey];
if(isdir&&[isdir boolValue]) return nil;
@try
{ return [parser handleForEntryWithDictionary:resdict wantChecksum:YES]; }
@catch(id e)
{
lasterror=[XADException parseException:e];
if(error) *error=lasterror;
}
return nil;
}
-(NSData *)contentsOfEntry:(int)n
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
if(!dict) return [NSData data]; // Special case for files with only a resource fork
@try
{
CSHandle *handle=[parser handleForEntryWithDictionary:dict wantChecksum:YES];
if(!handle) [XADException raiseDecrunchException];
NSData *data=[handle remainingFileContents];
if([handle hasChecksum]&&![handle isChecksumCorrect]) [XADException raiseChecksumException];
return data;
}
@catch(id e)
{
lasterror=[XADException parseException:e];
}
return nil;
}
// Extraction functions
-(BOOL)extractTo:(NSString *)destination
{
return [self extractEntries:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[self numberOfEntries])] to:destination subArchives:NO];
}
-(BOOL)extractTo:(NSString *)destination subArchives:(BOOL)sub
{
return [self extractEntries:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[self numberOfEntries])] to:destination subArchives:sub];
}
-(BOOL)extractEntries:(NSIndexSet *)entryset to:(NSString *)destination
{
return [self extractEntries:entryset to:destination subArchives:NO];
}
-(BOOL)extractEntries:(NSIndexSet *)entryset to:(NSString *)destination subArchives:(BOOL)sub
{
extractsize=0;
totalsize=0;
for(NSUInteger i=[entryset firstIndex];i!=NSNotFound;i=[entryset indexGreaterThanIndex:i])
totalsize+=[self representativeSizeOfEntry:i];
int numentries=[entryset count];
[delegate archive:self extractionProgressFiles:0 of:numentries];
[delegate archive:self extractionProgressBytes:0 of:totalsize];
for(NSUInteger i=[entryset firstIndex];i!=NSNotFound;i=[entryset indexGreaterThanIndex:i])
{
BOOL res;
if(sub&&[self entryIsArchive:i])
{
@try { res=[self extractArchiveEntry:i to:destination]; }
@catch(id e) { res=NO; }
if(!res&&lasterror==XADDataFormatError) // Retry as regular file if the archive format was not known
{
res=[self extractEntry:i to:destination deferDirectories:YES];
}
}
else res=[self extractEntry:i to:destination deferDirectories:YES];
if(!res)
{
totalsize=0;
return NO;
}
extractsize+=[self representativeSizeOfEntry:i];
[delegate archive:self extractionProgressFiles:i+1 of:numentries];
[delegate archive:self extractionProgressBytes:extractsize of:totalsize];
}
[self updateAttributesForDeferredDirectories];
totalsize=0;
return YES;
}
-(BOOL)extractEntry:(int)n to:(NSString *)destination
{ return [self extractEntry:n to:destination deferDirectories:NO dataFork:YES resourceFork:YES]; }
-(BOOL)extractEntry:(int)n to:(NSString *)destination deferDirectories:(BOOL)defer
{ return [self extractEntry:n to:destination deferDirectories:defer dataFork:YES resourceFork:YES]; }
-(BOOL)extractEntry:(int)n to:(NSString *)destination deferDirectories:(BOOL)defer
resourceFork:(BOOL)resfork
{ return [self extractEntry:n to:destination deferDirectories:defer dataFork:YES resourceFork:resfork]; }
-(BOOL)extractEntry:(int)n to:(NSString *)destination deferDirectories:(BOOL)defer
dataFork:(BOOL)datafork resourceFork:(BOOL)resfork
{
if(datafork) [delegate archive:self extractionOfEntryWillStart:n];
NSString *name;
while(!(name=[self nameOfEntry:n]))
{
if(delegate)
{
XADAction action=[delegate archive:self nameDecodingDidFailForEntry:n
data:[[[self dataForkParserDictionaryForEntry:n] objectForKey:XADFileNameKey] data]];
if(action==XADSkipAction) return YES;
else if(action!=XADRetryAction)
{
lasterror=XADBreakError;
return NO;
}
}
else
{
lasterror=XADEncodingError;
return NO;
}
}
if(![name length]) return YES; // Silently ignore unnamed files (or more likely, directories).
NSString *destfile=[destination stringByAppendingPathComponent:name];
while(![self _extractEntry:n as:destfile deferDirectories:defer dataFork:datafork resourceFork:resfork])
{
if(lasterror==XADBreakError) return NO;
else if(delegate&&datafork)
{
XADAction action=[delegate archive:self extractionOfEntryDidFail:n error:lasterror];
if(action==XADSkipAction) return YES;
else if(action!=XADRetryAction) return NO;
}
else return NO;
}
if(datafork) [delegate archive:self extractionOfEntryDidSucceed:n];
return YES;
}
-(BOOL)extractArchiveEntry:(int)n to:(NSString *)destination
{
NSString *path=[destination stringByAppendingPathComponent:
[[self nameOfEntry:n] stringByDeletingLastPathComponent]];
for(;;)
{
XADError err;
XADArchive *subarchive=[[XADArchive alloc] initWithArchive:self entry:n
immediateExtractionTo:path subArchives:YES error:&err];
if(!subarchive)
{
lasterror=err;
}
else
{
err=[subarchive lastError];
if(err) lasterror=err;
}
BOOL res=subarchive&&![subarchive immediateExtractionFailed];
[subarchive release];
if(res) return YES;
else if(err==XADBreakError||err==XADDataFormatError) return NO;
else if(delegate)
{
XADAction action=[delegate archive:self extractionOfEntryDidFail:n error:err];
if(action==XADSkipAction) return YES;
else if(action!=XADRetryAction) return NO;
}
else return NO;
}
}
-(BOOL)_extractEntry:(int)n as:(NSString *)destfile deferDirectories:(BOOL)defer
dataFork:(BOOL)datafork resourceFork:(BOOL)resfork
{
for(;;)
{
XADError error=[unarchiver _ensureDirectoryExists:[destfile stringByDeletingLastPathComponent]];
if(error==XADNoError)
{
break;
}
else if(delegate)
{
XADAction action=[delegate archive:self creatingDirectoryDidFailForEntry:n];
if(action==XADSkipAction) return YES;
else if(action!=XADRetryAction)
{
lasterror=XADBreakError;
return NO;
}
}
else
{
lasterror=error;
return NO;
}
}
struct stat st;
BOOL isdir=[self entryIsDirectory:n];
if(delegate)
while(lstat([destfile fileSystemRepresentation],&st)==0)
{
BOOL dir=(st.st_mode&S_IFMT)==S_IFDIR;
NSString *newname=nil;
XADAction action;
if(dir)
{
if(isdir) return YES;
else action=[delegate archive:self entry:n collidesWithDirectory:destfile newFilename:&newname];
}
else action=[delegate archive:self entry:n collidesWithFile:destfile newFilename:&newname];
if(action==XADOverwriteAction&&!dir) break;
else if(action==XADSkipAction) return YES;
else if(action==XADRenameAction) destfile=[[destfile stringByDeletingLastPathComponent] stringByAppendingPathComponent:newname];
else if(action!=XADRetryAction)
{
lasterror=XADBreakError;
return NO;
}
}
NSDictionary *datadict=[self dataForkParserDictionaryForEntry:n];
NSDictionary *resdict=[self resourceForkParserDictionaryForEntry:n];
//extractEntryWithDictionary:(NSDictionary *)dict as:(NSString *)path forceDirectories:(BOOL)force
if(datafork&&datadict)
{
extractingentry=n;
extractingresource=NO;
XADError error=[unarchiver extractEntryWithDictionary:datadict as:destfile forceDirectories:!defer];
if(error) { lasterror=error; return NO; }
}
if(resfork&&resdict)
{
extractingentry=n;
extractingresource=YES;
XADError error=[unarchiver extractEntryWithDictionary:resdict as:destfile forceDirectories:!defer];
if(error) { lasterror=error; return NO; }
}
return YES;
}
-(void)updateAttributesForDeferredDirectories
{
[unarchiver finishExtractions];
}
-(BOOL)extractionShouldStopForUnarchiver:(XADUnarchiver *)unarchiver;
{
return delegate&&[delegate archiveExtractionShouldStop:self];
}
-(void)unarchiver:(XADUnarchiver *)unarchiver extractionProgressForEntryWithDictionary:(NSDictionary *)dict
fileFraction:(double)fileprogress estimatedTotalFraction:(double)totalprogress
{
if(extractingresource) return;
off_t size=[self representativeSizeOfEntry:extractingentry];
off_t progress=fileprogress*size;
[delegate archive:self extractionProgressForEntry:extractingentry bytes:progress of:size];
if(totalsize)
{
[delegate archive:self extractionProgressBytes:extractsize+progress of:totalsize];
}
else if(immediatedestination)
{
[delegate archive:self extractionProgressBytes:totalprogress*immediatesize of:immediatesize];
}
}
-(NSString *)unarchiver:(XADUnarchiver *)unarchiver destinationForLink:(XADString *)link from:(NSString *)path
{
NSString *linkstring;
if(![link encodingIsKnown]&&delegate)
{
// TODO: should there be a better way to deal with encodings?
NSStringEncoding encoding=[delegate archive:self encodingForData:[link data]
guess:[link encoding] confidence:[link confidence]];
linkstring=[link stringWithEncoding:encoding];
}
else linkstring=[link string];
return linkstring;
}
-(BOOL)unarchiver:(XADUnarchiver *)unarchiver shouldCreateDirectory:(NSString *)directory
{
if(!delegate||[delegate archive:self shouldCreateDirectory:directory]) return YES;
else return NO;
}
-(void)unarchiver:(XADUnarchiver *)unarchiver didCreateDirectory:(NSString *)directory
{
if(delegate) {
[delegate archive:self didCreateDirectory:directory];
}
}
//
// Deprecated
//
-(int)sizeOfEntry:(int)n // deprecated and broken
{
NSDictionary *dict=[self dataForkParserDictionaryForEntry:n];
if(!dict) return 0; // Special case for resource forks without data forks
NSNumber *size=[dict objectForKey:XADFileSizeKey];
if(!size) return INT_MAX;
return [size intValue];
}
// Ugly hack to support old versions of Xee.
-(void *)xadFileInfoForEntry:(int)n
{
struct xadFileInfo
{
void *xfi_Next;
uint32_t xfi_EntryNumber;/* number of entry */
char *xfi_EntryInfo; /* additional archiver text */
void *xfi_PrivateInfo;/* client private, see XAD_OBJPRIVINFOSIZE */
uint32_t xfi_Flags; /* see XADFIF_xxx defines */
char *xfi_FileName; /* see XAD_OBJNAMESIZE tag */
char *xfi_Comment; /* see XAD_OBJCOMMENTSIZE tag */
uint32_t xfi_Protection; /* AmigaOS3 bits (including multiuser) */
uint32_t xfi_OwnerUID; /* user ID */
uint32_t xfi_OwnerGID; /* group ID */
char *xfi_UserName; /* user name */
char *xfi_GroupName; /* group name */
uint64_t xfi_Size; /* size of this file */
uint64_t xfi_GroupCrSize;/* crunched size of group */
uint64_t xfi_CrunchSize; /* crunched size */
char *xfi_LinkName; /* name and path of link */
struct xadDate {
uint32_t xd_Micros; /* values 0 to 999999 */
int32_t xd_Year; /* values 1 to 2147483648 */
uint8_t xd_Month; /* values 1 to 12 */
uint8_t xd_WeekDay; /* values 1 to 7 */
uint8_t xd_Day; /* values 1 to 31 */
uint8_t xd_Hour; /* values 0 to 23 */
uint8_t xd_Minute; /* values 0 to 59 */
uint8_t xd_Second; /* values 0 to 59 */
} xfi_Date;
uint16_t xfi_Generation; /* File Generation [0...0xFFFF] (V3) */
uint64_t xfi_DataPos; /* crunched data position (V3) */
void *xfi_MacFork; /* pointer to 2nd fork for Mac (V7) */
uint16_t xfi_UnixProtect;/* protection bits for Unix (V11) */
uint8_t xfi_DosProtect; /* protection bits for MS-DOS (V11) */
uint8_t xfi_FileType; /* XADFILETYPE to define type of exe files (V11) */
void *xfi_Special; /* pointer to special data (V11) */
};
NSDictionary *dict=[self combinedParserDictionaryForEntry:n];
NSDate *mod=[dict objectForKey:XADLastModificationDateKey];
NSNumber *size=[dict objectForKey:XADFileSizeKey];
NSMutableData *data=[NSMutableData dataWithLength:sizeof(struct xadFileInfo)];
struct xadFileInfo *fi=[data mutableBytes];
if(mod)
{
NSCalendarDate *cal=[mod dateWithCalendarFormat:nil timeZone:[NSTimeZone defaultTimeZone]];
fi->xfi_Date.xd_Year=[cal yearOfCommonEra];
fi->xfi_Date.xd_Month=[cal monthOfYear];
fi->xfi_Date.xd_Day=[cal dayOfMonth];
fi->xfi_Date.xd_Hour=[cal hourOfDay];
fi->xfi_Date.xd_Minute=[cal minuteOfHour];
fi->xfi_Date.xd_Second=[cal secondOfMinute];
}
else fi->xfi_Flags|=1<<6;
if(size) fi->xfi_Size=[size longLongValue];
else fi->xfi_Size=0;
return fi;
}
-(BOOL)extractEntry:(int)n to:(NSString *)destination overrideWritePermissions:(BOOL)override
{ return [self extractEntry:n to:destination deferDirectories:override resourceFork:YES]; }
-(BOOL)extractEntry:(int)n to:(NSString *)destination overrideWritePermissions:(BOOL)override resourceFork:(BOOL)resfork
{ return [self extractEntry:n to:destination deferDirectories:override resourceFork:resfork]; }
-(void)fixWritePermissions { [self updateAttributesForDeferredDirectories]; }
-(NSStringEncoding)archive:(XADArchive *)archive encodingForData:(NSData *)data guess:(NSStringEncoding)guess confidence:(float)confidence
{ return [delegate archive:archive encodingForData:data guess:guess confidence:confidence]; }
-(XADAction)archive:(XADArchive *)archive nameDecodingDidFailForEntry:(int)n data:(NSData *)data
{ return [delegate archive:archive nameDecodingDidFailForEntry:n data:data]; }
-(BOOL)archiveExtractionShouldStop:(XADArchive *)arc
{ return [delegate archiveExtractionShouldStop:arc]; }
-(BOOL)archive:(XADArchive *)arc shouldCreateDirectory:(NSString *)directory
{ return [delegate archive:arc shouldCreateDirectory:directory]; }
-(void)archive:(XADArchive *)arc didCreateDirectory:(NSString *)directory
{ [delegate archive:arc didCreateDirectory:directory]; }
-(XADAction)archive:(XADArchive *)arc entry:(int)n collidesWithFile:(NSString *)file newFilename:(NSString **)newname
{ return [delegate archive:arc entry:n collidesWithFile:file newFilename:newname]; }
-(XADAction)archive:(XADArchive *)arc entry:(int)n collidesWithDirectory:(NSString *)file newFilename:(NSString **)newname
{ return [delegate archive:arc entry:n collidesWithDirectory:file newFilename:newname]; }
-(XADAction)archive:(XADArchive *)arc creatingDirectoryDidFailForEntry:(int)n
{ return [delegate archive:arc creatingDirectoryDidFailForEntry:n]; }
-(void)archiveNeedsPassword:(XADArchive *)arc
{ [delegate archiveNeedsPassword:arc]; }
-(void)archive:(XADArchive *)arc extractionOfEntryWillStart:(int)n
{ [delegate archive:arc extractionOfEntryWillStart:n]; }
-(void)archive:(XADArchive *)arc extractionProgressForEntry:(int)n bytes:(off_t)bytes of:(off_t)total
{ [delegate archive:arc extractionProgressForEntry:n bytes:bytes of:total]; }
-(void)archive:(XADArchive *)arc extractionOfEntryDidSucceed:(int)n
{ [delegate archive:arc extractionOfEntryDidSucceed:n]; }
-(XADAction)archive:(XADArchive *)arc extractionOfEntryDidFail:(int)n error:(XADError)error
{ return [delegate archive:arc extractionOfEntryDidFail:n error:error]; }
-(XADAction)archive:(XADArchive *)arc extractionOfResourceForkForEntryDidFail:(int)n error:(XADError)error
{ return [delegate archive:arc extractionOfResourceForkForEntryDidFail:n error:error]; }
-(void)archive:(XADArchive *)arc extractionProgressBytes:(off_t)bytes of:(off_t)total
{ [delegate archive:arc extractionProgressBytes:bytes of:total]; }
//-(void)archive:(XADArchive *)arc extractionProgressFiles:(int)files of:(int)total;
//{}
@end
@implementation NSObject (XADArchiveDelegate)
-(NSStringEncoding)archive:(XADArchive *)archive encodingForData:(NSData *)data guess:(NSStringEncoding)guess confidence:(float)confidence
{
// Default implementation calls old method
NSMutableData *terminateddata=[[NSMutableData alloc] initWithData:data];
[terminateddata increaseLengthBy:1]; // append a 0 byte
NSStringEncoding enc=[self archive:archive encodingForName:[terminateddata bytes] guess:guess confidence:confidence];
[terminateddata release];
return enc;
}
-(XADAction)archive:(XADArchive *)archive nameDecodingDidFailForEntry:(int)n data:(NSData *)data
{
// Default implementation calls old method
NSMutableData *terminateddata=[[NSMutableData alloc] initWithData:data];
XADAction action=[self archive:archive nameDecodingDidFailForEntry:n bytes:[terminateddata bytes]];
[terminateddata release];
return action;
}
-(BOOL)archiveExtractionShouldStop:(XADArchive *)archive { return NO; }
-(BOOL)archive:(XADArchive *)archive shouldCreateDirectory:(NSString *)directory { return YES; }
-(void)archive:(XADArchive *)archive didCreateDirectory:(NSString *)directory { }
-(XADAction)archive:(XADArchive *)archive entry:(int)n collidesWithFile:(NSString *)file newFilename:(NSString **)newname { return XADOverwriteAction; }
-(XADAction)archive:(XADArchive *)archive entry:(int)n collidesWithDirectory:(NSString *)file newFilename:(NSString **)newname { return XADSkipAction; }
-(XADAction)archive:(XADArchive *)archive creatingDirectoryDidFailForEntry:(int)n { return XADAbortAction; }
-(void)archiveNeedsPassword:(XADArchive *)archive {}
-(void)archive:(XADArchive *)archive extractionOfEntryWillStart:(int)n {}
-(void)archive:(XADArchive *)archive extractionProgressForEntry:(int)n bytes:(off_t)bytes of:(off_t)total {}
-(void)archive:(XADArchive *)archive extractionOfEntryDidSucceed:(int)n {}
-(XADAction)archive:(XADArchive *)archive extractionOfEntryDidFail:(int)n error:(XADError)error { return XADAbortAction; }
-(XADAction)archive:(XADArchive *)archive extractionOfResourceForkForEntryDidFail:(int)n error:(XADError)error { return XADAbortAction; }
-(void)archive:(XADArchive *)archive extractionProgressBytes:(off_t)bytes of:(off_t)total {}
-(void)archive:(XADArchive *)archive extractionProgressFiles:(int)files of:(int)total {}
// Deprecated
-(NSStringEncoding)archive:(XADArchive *)archive encodingForName:(const char *)bytes guess:(NSStringEncoding)guess confidence:(float)confidence { return guess; }
-(XADAction)archive:(XADArchive *)archive nameDecodingDidFailForEntry:(int)n bytes:(const char *)bytes { return XADAbortAction; }
@end