cog/Frameworks/Sparkle/SUDiskImageUnarchiver.m

207 lines
6.9 KiB
Objective-C

//
// SUDiskImageUnarchiver.m
// Sparkle
//
// Created by Andy Matuschak on 6/16/08.
// Copyright 2008 Andy Matuschak. All rights reserved.
//
#import "SUDiskImageUnarchiver.h"
#import "SUUnarchiver_Private.h"
#import "NTSynchronousTask.h"
#import "SULog.h"
#import <CoreServices/CoreServices.h>
@implementation SUDiskImageUnarchiver
+ (BOOL)canUnarchivePath:(NSString *)path
{
return [[path pathExtension] isEqualToString:@"dmg"];
}
// Called on a non-main thread.
- (void)extractDMG
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSData *result = [NTSynchronousTask task:@"/usr/bin/hdiutil" directory:@"/" withArgs:[NSArray arrayWithObjects: @"isencrypted", archivePath, nil] input:NULL];
if([self isEncrypted:result] && [delegate respondsToSelector:@selector(unarchiver:requiresPasswordReturnedViaInvocation:)]) {
[self performSelectorOnMainThread:@selector(requestPasswordFromDelegate) withObject:nil waitUntilDone:NO];
} else {
[self extractDMGWithPassword:nil];
}
[pool release];
}
// Called on a non-main thread.
- (void)extractDMGWithPassword:(NSString *)password
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL mountedSuccessfully = NO;
SULog(@"Extracting %@ as a DMG", archivePath);
// get a unique mount point path
NSString *mountPointName = nil;
NSString *mountPoint = nil;
FSRef tmpRef;
do
{
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid)
{
CFStringRef uuidString = CFUUIDCreateString(NULL, uuid);
if (uuidString)
{
mountPoint = [@"/Volumes" stringByAppendingPathComponent:(NSString*)uuidString];
CFRelease(uuidString);
}
CFRelease(uuid);
}
}
while (noErr == FSPathMakeRefWithOptions((UInt8 *)[mountPoint fileSystemRepresentation], kFSPathMakeRefDoNotFollowLeafSymlink, &tmpRef, NULL));
NSData *promptData = nil;
if (password) {
NSString *data = [NSString stringWithFormat:@"%@\nyes\n", password];
const char *bytes = [data cStringUsingEncoding:NSUTF8StringEncoding];
NSUInteger length = [data lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
promptData = [NSData dataWithBytes:bytes length:length];
}
else
promptData = [NSData dataWithBytes:"yes\n" length:4];
NSArray* arguments = [NSArray arrayWithObjects:@"attach", archivePath, @"-mountpoint", mountPoint, /*@"-noverify",*/ @"-nobrowse", @"-noautoopen", nil];
NSData *output = nil;
NSInteger taskResult = -1;
@try
{
NTSynchronousTask* task = [[NTSynchronousTask alloc] init];
[task run:@"/usr/bin/hdiutil" directory:@"/" withArgs:arguments input:promptData];
taskResult = [task result];
output = [[[task output] copy] autorelease];
[task release];
}
@catch (NSException *localException)
{
goto reportError;
}
if (taskResult != 0)
{
NSString* resultStr = output ? [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease] : nil;
if (password != nil && [resultStr rangeOfString:@"Authentication error"].location != NSNotFound && [delegate respondsToSelector:@selector(unarchiver:requiresPasswordReturnedViaInvocation:)]) {
[self performSelectorOnMainThread:@selector(requestPasswordFromDelegate) withObject:nil waitUntilDone:NO];
goto finally;
} else {
SULog( @"hdiutil failed with code: %d data: <<%@>>", taskResult, resultStr );
goto reportError;
}
}
mountedSuccessfully = YES;
// Now that we've mounted it, we need to copy out its contents.
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) {
// On 10.7 and later we don't want to use the File Manager API and instead want to use NSFileManager (fixes #827357).
NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
NSError *error = nil;
NSArray *contents = [manager contentsOfDirectoryAtPath:mountPoint error:&error];
if (error)
{
SULog(@"Couldn't enumerate contents of archive mounted at %@: %@", mountPoint, error);
goto reportError;
}
NSEnumerator *contentsEnumerator = [contents objectEnumerator];
NSString *item;
while ((item = [contentsEnumerator nextObject]))
{
NSString *fromPath = [mountPoint stringByAppendingPathComponent:item];
NSString *toPath = [[archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:item];
// We skip any files in the DMG which are not readable.
if (![manager isReadableFileAtPath:fromPath])
continue;
SULog(@"copyItemAtPath:%@ toPath:%@", fromPath, toPath);
if (![manager copyItemAtPath:fromPath toPath:toPath error:&error])
{
SULog(@"Couldn't copy item: %@ : %@", error, error.userInfo ? error.userInfo : @"");
goto reportError;
}
}
}
else {
FSRef srcRef, dstRef;
OSStatus err;
err = FSPathMakeRef((UInt8 *)[mountPoint fileSystemRepresentation], &srcRef, NULL);
if (err != noErr) goto reportError;
err = FSPathMakeRef((UInt8 *)[[archivePath stringByDeletingLastPathComponent] fileSystemRepresentation], &dstRef, NULL);
if (err != noErr) goto reportError;
err = FSCopyObjectSync(&srcRef, &dstRef, (CFStringRef)mountPointName, NULL, kFSFileOperationSkipSourcePermissionErrors);
if (err != noErr) goto reportError;
}
[self performSelectorOnMainThread:@selector(notifyDelegateOfSuccess) withObject:nil waitUntilDone:NO];
goto finally;
reportError:
[self performSelectorOnMainThread:@selector(notifyDelegateOfFailure) withObject:nil waitUntilDone:NO];
finally:
if (mountedSuccessfully)
[NSTask launchedTaskWithLaunchPath:@"/usr/bin/hdiutil" arguments:[NSArray arrayWithObjects:@"detach", mountPoint, @"-force", nil]];
else
SULog(@"Can't mount DMG %@",archivePath);
[pool drain];
}
- (void)start
{
[NSThread detachNewThreadSelector:@selector(extractDMG) toTarget:self withObject:nil];
}
+ (void)load
{
[self registerImplementation:self];
}
- (BOOL)isEncrypted:(NSData*)resultData
{
BOOL result = NO;
if(resultData)
{
NSString *data = [NSString stringWithCString:(char*)[resultData bytes] encoding:NSUTF8StringEncoding];
if (!NSEqualRanges([data rangeOfString:@"passphrase-count"], NSMakeRange(NSNotFound, 0)))
{
result = YES;
}
}
return result;
}
- (void)requestPasswordFromDelegate
{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(continueWithPassword:)]];
[invocation setSelector:@selector(continueWithPassword:)];
[invocation setTarget:self];
[invocation retainArguments];
[delegate unarchiver:self requiresPasswordReturnedViaInvocation:invocation];
}
- (void)continueWithPassword:(NSString *)password
{
[NSThread detachNewThreadSelector:@selector(extractDMGWithPassword:) toTarget:self withObject:password];
}
@end