Skip to content
This repository

XPC service for Lion Sandboxing #165

Open
wants to merge 1 commit into from
Whitney Young

This fixes #163. It requires that you copy the XPC service into your main application bundle in Contents/XPCServices and runs the remove from quarantine step of copying the finish_installation app as well as launching finish_installation app outside of the sandbox. Sparkle will function exactly as it does now for applications that do not copy the XPC service into their application bundle.

Whitney Young Added an XPC service to deal with sandboxing.
This fixes #163. It requires that you copy the XPC service into your main application bundle in
Contents/XPCServices and runs the remove from quarantine step of copying the finish_installation
app as well as launching finish_installation app outside of the sandbox. Sparkle will function
exactly as it does now for applications that do not copy the XPC service into their application
bundle.
dbd2b9b
Andy Matuschak
Owner
Andy Matuschak
Owner

Thanks very much for the patch. I have made only a first pass at review, as I am seriously writing this from a beach on Maui.

I don't think that the launch_task command of the service can be permitted to be so general. An exploit in the host app could use this to execute an arbitrary task outside the sandbox—no good! I think this could be remedied by expressing the path to finish_installation in relative terms from the service. We'd also need to ensure something like the path being installed and the path being updated are signed with the same code signing identity as the parent app.

I'm also concerned about the usage of synchronous XPC messages. We can't block the main thread of the client app in that way.

While I don't presently have time to handle these issues myself, I would be happy to assist you in doing so if needed. Thanks again for taking the time to do this.

Whitney Young

I'll try to work on some of these pieces when I get time. I've been pretty busy recently, though. Any help would be appreciated!

Some thoughts:

How should the launch_task command be specified relatively? Should there be just a single argument passed to the service that is the name of the finish_installation app (since it could be renamed) and allow the launch_task to figure out the path to the application support folder? I think this should mostly work, but are there cases where you can customize things that might change which bundle is used for calculating the application support folder name (or anything like that)?

The code signing part makes sense as well, but is there a good way to extract information about who signed a binary? All I know is codesign, but that won't work. Is there an API for this?

In testing the synchronous XPC messages weren't really a problem. I guess there's a possibility of a hang, but there's also file IO going on in the same method which could hang as well. I can't test right now, but is this method running on the main thread anyway?

Andy Matuschak
Owner
Whitney Young

Seems good. I'll take a look when I get a chance.

It seems the better way to go would be to verify that the finish_installation binary is code-signed with the same key as the client application. This will provide better security than using a checksum (which would still allow someone to pad an executable to match the hash). At that point, the path tot the executable doesn't really matter, would you agree? I'll take a look at the API and try to get that together.

I didn't know that the _sync variants were deprecated. Thanks for letting me know. I'll refactor and get that fixed for sure.

Fraser Hess

Hello,
I've been looking over this code with the hope that it could help bail me out of a difficult decision (whether or not to go App Store-only) and I have a couple of comments:

  • I think Andy is right regarding the synchronous XPC calls. The documentation is very clear.
  • I'm not sure I agree about needing to authenticate the caller of the XPC service. While it's not the best, the rest of app is running in a sandbox and that's a significant security improvement over the non-sandbox alternative.
  • In general, Whitney's solution is a valiant effort to solve a difficult problem, with surprisingly little code. The problem occurs when trying to graft an asynchronous API (XPC) into places where Sparkle uses synchronous APIs.
  • There are no xpc_release() calls and therefore, memory leaks are occurring.
  • I'm thinking the solution probably involves another branch of Sparkle, where in the parts that need to be reworked for asynchronous calls are substantially different as opposed to more conditionals all over the place. And yes, that would have to be Lion only.
Whitney Young
wbyoung commented May 30, 2012

Fraser,

Good points. Since the only XPC calls are in a function returning void, there wouldn't be that much more work to make it async. I don't think another branch would be necessary.

Sam Deane

Did this work get any further? Happy to try to help, but it would be handy to know that I'm not duplicating effort.

Sam Deane

Looking at the code, it seems like installWithToolAndRelaunch should be easy enough to split into two, with the completion of the copying triggering the second part.

However, there are two issues.

First, the existing file copying code is effectively synchronous, so I'm not convinced that there's an absolute requirement to make the xpc one async. I guess that this is a good thing...

Second, the last thing that the routine does is to terminate the app. If this happens before the launch task has completed, there might be an issue with the xpc service - are we sure that it will survive the app that launched it going away?

Perhaps more to the point, if we make either of the xpc calls asynchronous, that means that the original installWithToolAndRelaunch needs to return without having called NSApp terminate. This presumably has the potential to change the behaviour of whatever called it, which might lead to all sorts of mess.

Anyone got any comments? Are my concerns relevant - bearing in mind that I don't know this code base at all!

Whitney Young

Sam,

I think you'd want to terminate after the XPC service completed. I believe that'd be possible, but this was the first time that I worked with XPC, so I don't know for sure.

Other than that I think Andy would know better because he'd be more familiar with the code base.

I probably won't be finishing the work on this now, though. We just recently decided that the App we were using this for with sandboxing will be only sold through the Mac App Store now.

Sam Deane

Thanks Whitney. I think you're right, the main code should wait until the XPC service completes.

This means that installWithToolAndRelaunch would return before the launching had completed. Hopefully that won't cause problems, but maybe Andy can say.

Andy Matuschak
Owner

w.r.t. the security-in-XPC-service issue, I expect that the new bits in https://github.com/andymatuschak/Sparkle/tree/CodeSigning should help a great deal!

Andy Matuschak
Owner

It's true that making the XPC calls asynchronous will change the code flow somewhat, but I don't think that will be particularly troublesome to any of the callers.

Lucien W. Dupont

Can I help out on getting this working and secure? I've got some time available in the next few weeks.

Sam Deane

@lwdupont go for it - I was planning on having a look at it next week, but I won't have time before then, and something else will probably come up to distract me!

Lucien W. Dupont

@samdeane Ok, thanks. I'll probably give it a start tomorrow morning.

Erik Aderstedt

I changed wbyoung's patch to make the XPC calls asynchronous (commit here). A few issues remain:

  • The host app still needs entitlements for full network access. If I want to use the same entitlements file for the App Store and non-App Store versions of my app (and I do), this seems overly excessive. This means I need to put all the URL downloading code in an XPC service as well.
  • The launch_task command is still quite general. I don't know if this is a big problem or not.
Erik Aderstedt

Ok, so now I put the networking code in an XPC as well. This means that Sparkle will not require any extra entitlements; the Sparkle Test App runs without any entitlements at all. You can find the branch here. I've also made a few config changes to get Sparkle to build in Xcode 4.4 on 10.7; my git-fu is too weak to split this up after the fact, sorry.

So, how does it work?

  • I added a new service com.andymatuschak.Sparkle.download-service. This builds on the SandboxedFetch sample from Apple.
  • I added a class SUXPCURLDownload, that emulates NSURLDownload and calls the same delegate methods.
  • The SUXPCURLDownload class talks to a new XPC that internally uses libcurl.

Limitations:

  • Remote release note URLs are not supported.
  • https:// is not supported because libcurl doesn't do this out-of-the-box. Can probably be added if required. Now we all sign apps with Developer-ID anyway, right?
  • Not tested in any case other than the default Sparkle Test App case.
Sam Deane

Hi Erik, I'm going to give this a go for a new beta build of Ambientweet. Anything I should watch out for?

Sam Deane

Just tested this in the wild.

Running a debug build under Xcode worked fine - it successfully downloaded and installed.

However, running a release build outside of Xcode resulted in this:

Process: Ambientweet FastSpring [57842]
Path: /Users/USER/*/Ambientweet.app/Contents/MacOS/Ambientweet FastSpring
Identifier: Ambientweet FastSpring
Version: 1.1.2b1 (107)
Code Type: X86-64 (Native)
Parent Process: Ambientweet FastSpring [57828]
User ID: 502

Date/Time: 2012-08-22 18:19:30.615 +0100
OS Version: Mac OS X 10.8 (12A269)
Report Version: 10

Crashed Thread: 0 Dispatch queue: com.apple.main-thread

Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000002, 0x0000000000000000

Application Specific Information:
*** NSTask: Task create for path '/Volumes/titan/Users/sam/Library/Containers/com.elegantchaos.ambientweet/Data/Library/Application Support/Ambientweet/finish_installation.app/Contents/MacOS/finish_installation' failed: 22, "Invalid argument". Terminating temporary process.
Performing @selector(installAndRestart:) from sender NSButton 0x1002d4d90
*** multi-threaded process forked ***

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 com.apple.Foundation 0x00007fff8cd3e488 NEW_PROCESS_COULD_NOT_BE_EXECD + 5
1 com.apple.Foundation 0x00007fff8cc17b41 -[NSConcreteTask launchWithDictionary:] + 3544
2 com.apple.Foundation 0x00007fff8cc1661d +[NSTask launchedTaskWithLaunchPath:arguments:] + 205
3 org.andymatuschak.Sparkle 0x0000000100097345 0x100089000 + 58181
4 org.andymatuschak.Sparkle 0x0000000100096d1e 0x100089000 + 56606
5 org.andymatuschak.Sparkle 0x000000010009886b 0x100089000 + 63595
6 com.apple.AppKit 0x00007fff88831219 -[NSApplication sendAction:to:from:] + 342
7 com.apple.AppKit 0x00007fff88831077 -[NSControl sendAction:to:] + 85
8 com.apple.AppKit 0x00007fff88830fab -[NSCell _sendActionFrom:] + 138
9 com.apple.AppKit 0x00007fff8882f493 -[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 1855
10 com.apple.AppKit 0x00007fff8882ece1 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 504
11 com.apple.AppKit 0x00007fff8882e45c -[NSControl mouseDown:] + 820
12 com.apple.AppKit 0x00007fff88825dce -[NSWindow sendEvent:] + 6853
13 com.apple.AppKit 0x00007fff88821f04 -[NSApplication sendEvent:] + 5761
14 com.apple.AppKit 0x00007fff88737c7a -[NSApplication run] + 636
15 com.apple.AppKit 0x00007fff886dc656 NSApplicationMain + 869
16 com.elegantchaos.ambientweet 0x0000000100001d04 start + 52

Erik Aderstedt

Do you see any sandbox violations in Console?

Sam Deane

Ah, I'm an idiot :)

I didn't add the XPC services to my application...

Sam Deane

It appears to be working now.

If you download http://downloads.elegantchaos.com/ambientweet/ambientweet-v1.1.2b2.zip, it should try to update to 1.1.2b3. Seems to work for me, but it would be good to hear if it's broken for others :)

Sam Deane

My version of @erikaderstedt's branch is samdeane/Sparkle branch:xpc-service (here)

It's pretty much identical at this point.

Rudy Richter

prodding andy to evaluate the submitted patch/pull request to see if it meets his approval for inclusion in master

Andy Matuschak

Erik, thanks so much for your work here! I've reviewed your branch, and aside from cosmetic issues, I have a few questions and a few concerns:

  • install_service is still insecure to the extent that it defeats the purpose of the parent app being in the sandbox in the first place: it's a privileged service which will copy an arbitrary file to an arbitrary place. That's not okay!
    • Since this is the privileged process, it must be the entity which verifies the integrity of the update, not the host (as is done now).
    • This service should ensure that the update satisfies the host's designated requirement. Check out the CodeSigning branch's SUCodeSigningVerifier.
    • Since the parent app has read/write access to the update path, the install service must defend against symlink attacks which would allow an exploited parent app to swap a malicious update in immediately after the service verifies the code signature.
    • This can be accomplished by chdir()ing to the update path before performing the check, then using only relative paths to access anything in the update's path henceforth.
  • Why does the downloading service use libcurl, not NSURLDownload?
  • I like very much that you tried to make it so that the parent application doesn't have to have a network access entitlement, but I'm concerned that in general, the download service won't be sufficient for release notes: many of them have images, JavaScript, etc.
    • I guess we could document that if you're going to use "rich" release notes, your app must support full network access? Seems kind of lame, but maybe it's better than nothing.
  • Why do you pass the file descriptor of the downloaded file from the downloading service back to the host? It doesn't seem to do anything with it.
  • Why make another XPC connection to send progress reports on from the downloading service, rather than simply sending messages over the connection already established from the host?
  • I think we should prohibit the installation service from accessing the network via sandbox_init and probably prohibit the downloading service from accessing anything but /tmp via same.

I may have time this or next weekend to try to help finish this off, if that'd be useful. Thanks again for your contributions here!

Erik Aderstedt
Andy Matuschak

Thanks for the answers, Erik. I understand now why you weren't too concerned about the security ramifications—that makes sense. I'm thinking about it more for apps which never ship on the App Store, but which are still sandboxed; they should still be secure! I'll try to make time to keep iterating on this soon.

Sush.io

Hi there,

Thanks for all this very exciting and interesting discussion.
Is someone can provide a simple process o follow to be able to use sparkle in a sandboxed environment? For exemple, I have absolutly no idea how I can create or retrieve XPC-service and define a class susch as SUXPCURLDownload, that emulates NSURLDownload.

Thank you!

Sush.io

ok, after looking for by myself, solved, everything work fine for me too.
just forgot to clone the good repo... thanks to samdeane for this very helpful work.

Sam Deane

would love to take the credit, but it was @erikaderstedt who did the work!

Sam Deane

and @wbyoung before that!

Sush.io
Ryan Nielsen

These changes have worked quite well, in our testing. Thank you for taking point on this @erikaderstedt and @wbyoung!

I have two diffs that I strongly encourage you to take: tumult@82eaa63 and tumult@512f9d7

They should really be one diff, but I inadvertently pushed the wrong change initially.

By default, all XPC services create their own independent security session. However, with NSDocument based applications using Sparkle, this triggers what appears to be a bug in 10.8.

The newly updated app is launched and inherits the XPC service's security session. On launch, com.apple.security.pboxd is invoked (I presume to restore state if the app has documents saved outside of its sandbox that aren't tracked by security-scoped bookmarks?). Normally, if the app is in the user's security session, this is an invisible, unremarkable event. However, in the special case of the app being launched by Sparkle's XPC service living in its own security session, this pboxd invocation actually appears as a running process in the Dock that looks identical to the newly launched app.

After following a ton of dead ends, it turns out fixing this issue is quite simple: Sparkle's XPC service needs to declare it should exist in the invoking app's security session. This is done easily, by setting JoinExistingSession to YES in the XPCService dictionary of the service's plist.

I created a test project that can be used to verify this change: https://github.com/ryannielsen/NSDocumentSandboxSparkleTest

That project will pull from our Sparkle fork https://github.com/tumult/Sparkle when you do a submodule init/update. The JoinExistingSessions key will be set to YES, so you'll need to flip that to NO if you wish to reproduce the bug.

After that one minor change, things seem to be working perfectly!

Ryan Nielsen

(Since it feels the behavior I've seen is a bug – I'd either expect launching the app in its own security session to be invalid and thus it shouldn't appear as a running app in the user's login session; or that is completely valid and thus com.apple.security.pboxd should not appear as a second, apparently broken instance of the launched app – I filed a bug with Apple. radar://12648274, for those who care and can view.)

Andy Matuschak

Hm, but that means that the XPC service can't have a disjoint set of permissions from its host…

Dobre Claudiu

Hello! Thank you for providing a solution for this issue with the Sandbox, but I still can't manage to make my app update with the Sandbox.
I got the test project from https://github.com/ryannielsen/NSDocumentSandboxSparkleTest , but on update it says that "An error occurred in retrieving update information. Please try again later.", have anyone tried this project and works for him? (In this project the Sparkle is missing and I copied from here the Sparkle project, is possible that I don't have the right Sparkle?)
There some new changes from Apple and this solution doesn't work anymore?

What are the steps that should I follow exactly...?

Thank you.

Ryan Nielsen

@andymatuschak I want to say (though I may be mis-remembering) that we tested Sparkle updating with our app being sandboxed and the Sparkle XPCServices being signed but not sandboxed and we were able to successfully upgrade, so that would imply that disjoint permissions are allowed even though the security sessions are different.

@ClaudiuD Did you have a server running which was hosting the appcast? The solution I described earlier is definitely still working. We're using it in our app right now.

Dobre Claudiu

Thank you for the response. No, what files should I add to my server?
On my real app, I have the dmg and the xml file on the server, for making the update.

I am happy that your solution works, because I really need to implement it on my app.

Ryan Nielsen

The README.md file in https://github.com/ryannielsen/NSDocumentSandboxSparkleTest describes how to create the test… read the last two paragraphs and the step-by-step instructions at the bottom. In a nutshell, you need

1) A server that's hosting a valid appcast with a "new" version of the test app
2) The test app needs to know the hostname and url for the app cast
3) You need to run an "older" version of the test app that will cause Sparkle to trigger an update

Without those three things, the test will fail.

Dobre Claudiu

I implemented this solution to my project too and I respected all the steps, but still that error.
I am using Mac OS 10.7 ... works only for Mac OS 10.8?

Dobre Claudiu

So start again.... I take the Sparkle project from this location https://github.com/tumult/Sparkle (is this the right version?) and I copy in the folder Sparkle from your test app. I change the server base url path constantes and current version of project to 2 and build, but not Appcast folder on the desktop.... I am doing something wrong?

Dobre Claudiu

Can you please tell me the steps I need to follow for integrating this Sparkle on my project.
Because I did all of this and I see the service on the build for it still doesn't work and I really need to make this update.

I don't know what could be wrong...

Dobre Claudiu

On console I get, for my project, not the test project:
1/9/13 5:50:25.630 PM sandboxd: ([26997]) hdiutil(26997) deny sysctl-write
1/9/13 5:50:25.945 PM sandboxd: ([26997]) hdiutil(26997) deny sysctl-write
1/9/13 5:50:26.334 PM sandboxd: ([26999]) hdiutil(26999) deny sysctl-write
1/9/13 5:50:26.354 PM sandboxd: ([26999]) hdiutil(26999) deny sysctl-write
1/9/13 5:50:26.441 PM sandboxd: ([27001]) diskimages-helpe(27001) deny mach-lookup BE03ABED-723B-4772-B435-9B734C05E3A4
1/9/13 5:50:26.442 PM diskimages-helper: ERROR: couldn't connect to framework.
1/9/13 5:50:26.442 PM diskimages-helper: DIHelper: setupConnectionToFrameworkWithUUID: failed

Its clearly that I don't have permissions to write, because of the Sandbox, but how this service starts on non-sandbox....
I respected the steps, but I am not sure if the service starts...

Ryan Nielsen

Hi @ClaudiuD this isn't the proper venue for diagnosing what's going wrong with my example project. I'm also afraid I cannot offer support for the project. I created it to ensure the changes in this pull request would meet our needs for our project, nothing more and nothing less.

A quick google on "diskimages sandbox" returns http://stackoverflow.com/questions/9119191/sandbox-and-nstask, which then led me to search devforums.apple.com. At the bottom of this thread, you'll see what's causing problems: https://devforums.apple.com/message/745710

It seems that sandboxed apps won't be able to use dmgs as their archive and distribution mechanism. The Sparkle changes it tests work for us on our app which is sandboxed on 10.7 and 10.8, but we distribute zip files.

Dobre Claudiu

Thank you for you answer. You save me a lot of time and nerves :) . Best.

Dobre Claudiu

This Sparkle works with zip and in my project too. Great job.
Thank you.

Billy Gray

Fantastic work! Gonna give a shot.

Sam Deane

@andymatuschak: would it be worth making a branch for this stuff on your repo, if you don't have the time to actually review the changes and merge them into your master branch?

That way people have an obvious place to go to get the latest sandbox-friendly version. At the moment it's a bit confusing - you have to read all the way through this thread to work out which commit to take from where.

I've got a "sandboxing" branch on my fork which I'm going to attempt to use for the latest version of Neu, but I'm a not 100% certain at the moment that I've picked up everything I should :)

Andy Matuschak
Sam Deane

Ok, that makes sense, and I wasn't meaning to imply that you were slacking ;)

It looks like some additional work has been done since you posted those comments. If we could collectively figure out a list of jobs still to be done to get it to an acceptable state for merging, then that would increase the likelihood of one of us picking up the baton and doing those jobs.

Personally, I'm in a similar position to Erik. I want to sandbox the non-App store version of my app, just for the sake of parity with the App store version, and so that I don't have to maintain two wildly different code-paths.

Of course I'd like it to be secure ultimately, but getting a new version of my app out there is higher priority in the short term. Of course, as long as Sparkle is working, I can update the app as Sparkle's sandboxing support gets improved.

Sam Deane

It seems to me that one of the main vulnerabilities could be eliminated by simply not passing the path of finish_installation to the XPC service. That stops it from being an arbitrary copying tool. Can't the service figure that out for itself (it should be a fixed relative path from the xpc service's own bundle location, as Andy mentioned in an earlier post).

For the other vulnerability, is it enough to have finish_installation verify that the package that it's going to install, the package it's going to replace, and itself were all signed by the same entity? This seems like it ought to be a way to generalise the code so that no information needs to be passed in other than the location of the package to be installed. An exploited app can do whatever it wants with the package or the path to it, but it won't be able to make these signatures match.

What am I missing?

Sam Deane

It seems to me that an even simpler approach would be to launch the finish_installation tool in the downloaded bundle directly. I don't think it would have a problem with copying the application into place (including itself) even though it's running. The only problem it would have would be in deleting the downloaded bundle afterwards, since it would be inside it. However, this step could just as easily be performed by the updated app, the next time it launched.

I'm probably missing something with this idea, but it would eliminate the need for an xpc helper at all.

Sam Deane

FWIW, I also wonder if this might be the point to perform some more radical pruning of the code.

I added an issue: #248

Andy Matuschak
Nicolas Giannetta

Is there a fork of Sparkle that supports Sandboxing with CodeSigning right now? I could really use this in my project. I'm distributing a private beta of an app to beta testers and this would be really helpful.

Mike Abdullah mikeabdullah referenced this pull request from a commit in karelia/Sparkle October 31, 2012
Ryan Nielsen Merge in changes for sandboxing and XPC
Pull request here: andymatuschak#165
Changes here: wbyoung@dbd2b9b
ced3a44
Mike Abdullah mikeabdullah referenced this pull request from a commit in karelia/Sparkle March 21, 2013
Patrick Burleson Merge commit 'ea50a33b46ceeef19b92534a29cc8d3463f67b23' into sandbox-…
…merge

Merging in Sam Dean's sandboxing branch which contains the work to get Sparkle to work with sandboxing done by several folks in this pull request thread: andymatuschak#165

Resolved with changes from upstream
0ffdc85
Kornel pornel referenced this pull request in pornel/Sparkle March 11, 2014
Open

Support for Sandboxing #8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Mar 20, 2012
Whitney Young Added an XPC service to deal with sandboxing.
This fixes #163. It requires that you copy the XPC service into your main application bundle in
Contents/XPCServices and runs the remove from quarantine step of copying the finish_installation
app as well as launching finish_installation app outside of the sandbox. Sparkle will function
exactly as it does now for applications that do not copy the XPC service into their application
bundle.
dbd2b9b
This page is out of date. Refresh to see the latest.
5  Configurations/ConfigService.xcconfig
... ...
@@ -0,0 +1,5 @@
  1
+// Sandbox Service only
  2
+
  3
+PRODUCT_NAME = com.andymatuschak.Sparkle.SandboxService
  4
+WRAPPER_EXTENSION = xpc
  5
+MACH_O_TYPE = mh_execute
5  Configurations/ConfigServiceDebug.xcconfig
... ...
@@ -0,0 +1,5 @@
  1
+#include "ConfigCommon.xcconfig"
  2
+#include "ConfigCommonDebug.xcconfig"
  3
+#include "ConfigService.xcconfig"
  4
+
  5
+OTHER_CFLAGS = -fsingle-precision-constant -DDEBUG
3  Configurations/ConfigServiceRelease.xcconfig
... ...
@@ -0,0 +1,3 @@
  1
+#include "ConfigCommon.xcconfig"
  2
+#include "ConfigCommonRelease.xcconfig"
  3
+#include "ConfigService.xcconfig"
18  SUBasicUpdateDriver.m
@@ -19,6 +19,7 @@
19 19
 #import "SUPlainInstallerInternals.h"
20 20
 #import "SUBinaryDeltaCommon.h"
21 21
 #import "SUUpdater_Private.h"
  22
+#import "SUXPC.h"
22 23
 
23 24
 @interface SUBasicUpdateDriver () <NSURLDownloadDelegate>; @end
24 25
 
@@ -272,6 +273,10 @@ - (void)installWithToolAndRelaunch:(BOOL)relaunch
272 273
         [self abortUpdate];
273 274
         return;
274 275
     }
  276
+	
  277
+	BOOL running10_7 = floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6;
  278
+	BOOL useXPC = running10_7 && [[NSFileManager defaultManager] fileExistsAtPath:
  279
+								  [[host bundlePath] stringByAppendingPathComponent:@"Contents/XPCServices/com.andymatuschak.Sparkle.SandboxService.xpc"]];
275 280
     
276 281
     // Give the host app an opportunity to postpone the install and relaunch.
277 282
     static BOOL postponedOnce = NO;
@@ -302,7 +307,12 @@ - (void)installWithToolAndRelaunch:(BOOL)relaunch
302 307
 #endif
303 308
 
304 309
 	// Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems.
305  
-	if( [SUPlainInstaller copyPathWithAuthentication: relaunchPathToCopy overPath: targetPath temporaryName: nil error: &error] )
  310
+	BOOL copiedRelaunchTool = FALSE;
  311
+	if( useXPC )
  312
+		copiedRelaunchTool = [SUXPC copyPathWithAuthentication: relaunchPathToCopy overPath: targetPath temporaryName: nil error: &error];
  313
+	else
  314
+		copiedRelaunchTool = [SUPlainInstaller copyPathWithAuthentication: relaunchPathToCopy overPath: targetPath temporaryName: nil error: &error];
  315
+	if( copiedRelaunchTool )
306 316
 		relaunchPath = [targetPath retain];
307 317
 	else
308 318
 		[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil), NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't copy relauncher (%@) to temporary path (%@)! %@", relaunchPathToCopy, targetPath, (error ? [error localizedDescription] : @"")], NSLocalizedFailureReasonErrorKey, nil]]];
@@ -323,7 +333,11 @@ - (void)installWithToolAndRelaunch:(BOOL)relaunch
323 333
     if ([[updater delegate] respondsToSelector:@selector(pathToRelaunchForUpdater:)])
324 334
         pathToRelaunch = [[updater delegate] pathToRelaunchForUpdater:updater];
325 335
     NSString *relaunchToolPath = [relaunchPath stringByAppendingPathComponent: @"/Contents/MacOS/finish_installation"];
326  
-    [NSTask launchedTaskWithLaunchPath: relaunchToolPath arguments:[NSArray arrayWithObjects:[host bundlePath], pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], tempDir, relaunch ? @"1" : @"0", nil]];
  336
+	NSArray *arguments = [NSArray arrayWithObjects:[host bundlePath], pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], tempDir, relaunch ? @"1" : @"0", nil];
  337
+	if( useXPC )
  338
+		[SUXPC launchTaskWithLaunchPath: relaunchToolPath arguments:arguments];
  339
+	else
  340
+		[NSTask launchedTaskWithLaunchPath: relaunchToolPath arguments:arguments];
327 341
 
328 342
     [NSApp terminate:self];
329 343
 }
5  SUPlainInstallerInternals.m
@@ -524,7 +524,10 @@ + (BOOL)copyPathWithAuthentication:(NSString *)src overPath:(NSString *)dst temp
524 524
 	// new home in case it's moved across filesystems: if that
525 525
 	// happens, the move is actually a copy, and it may result
526 526
 	// in the application being quarantined.
527  
-	[self performSelectorOnMainThread:@selector(releaseFromQuarantine:) withObject:dst waitUntilDone:YES];
  527
+	if ([NSThread isMultiThreaded])
  528
+		[self performSelectorOnMainThread:@selector(releaseFromQuarantine:) withObject:dst waitUntilDone:YES];
  529
+	else
  530
+		[self releaseFromQuarantine:dst];
528 531
 	
529 532
 	return YES;
530 533
 }
16  SUXPC.h
... ...
@@ -0,0 +1,16 @@
  1
+//
  2
+//  SUXPC.h
  3
+//  Sparkle
  4
+//
  5
+//  Created by Whitney Young on 3/19/12.
  6
+//  Copyright (c) 2012 FadingRed. All rights reserved.
  7
+//
  8
+
  9
+#import <Foundation/Foundation.h>
  10
+
  11
+@interface SUXPC : NSObject
  12
+
  13
++ (BOOL)copyPathWithAuthentication:(NSString *)src overPath:(NSString *)dst temporaryName:(NSString *)tmp error:(NSError **)error;
  14
++ (void)launchTaskWithLaunchPath:(NSString *)path arguments:(NSArray *)arguments;
  15
+
  16
+@end
72  SUXPC.m
... ...
@@ -0,0 +1,72 @@
  1
+//
  2
+//  SUXPC.m
  3
+//  Sparkle
  4
+//
  5
+//  Created by Whitney Young on 3/19/12.
  6
+//  Copyright (c) 2012 FadingRed. All rights reserved.
  7
+//
  8
+
  9
+#import <xpc/xpc.h>
  10
+#import "SUXPC.h"
  11
+
  12
+
  13
+@implementation SUXPC
  14
+
  15
++ (BOOL)copyPathWithAuthentication:(NSString *)src overPath:(NSString *)dst temporaryName:(NSString *)tmp error:(NSError **)error {
  16
+	xpc_connection_t connection = xpc_connection_create("com.andymatuschak.Sparkle.SandboxService", NULL);
  17
+	xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
  18
+		xpc_dictionary_apply(event, ^bool(const char *key, xpc_object_t value) {
  19
+			NSLog(@"XPC %s: %s", key, xpc_string_get_string_ptr(value));
  20
+			return true;
  21
+		});
  22
+	});
  23
+	xpc_connection_resume(connection);
  24
+
  25
+	xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
  26
+	xpc_dictionary_set_string(message, "id", "copy_path");
  27
+	
  28
+	if( src )
  29
+		xpc_dictionary_set_string(message, "source", [src fileSystemRepresentation]);
  30
+	if( dst )
  31
+		xpc_dictionary_set_string(message, "destination", [dst fileSystemRepresentation]);
  32
+	if( tmp )
  33
+		xpc_dictionary_set_string(message, "tmp", [tmp UTF8String]);
  34
+	
  35
+	xpc_object_t response = xpc_connection_send_message_with_reply_sync(connection, message);
  36
+	xpc_type_t type = xpc_get_type(response);
  37
+	return type == XPC_TYPE_DICTIONARY;
  38
+}
  39
+
  40
++ (void)launchTaskWithLaunchPath:(NSString *)path arguments:(NSArray *)arguments {
  41
+	xpc_connection_t connection = xpc_connection_create("com.andymatuschak.Sparkle.SandboxService", NULL);
  42
+	xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
  43
+		xpc_dictionary_apply(event, ^bool(const char *key, xpc_object_t value) {
  44
+			NSLog(@"XPC %s: %s", key, xpc_string_get_string_ptr(value));
  45
+			return true;
  46
+		});
  47
+	});
  48
+	xpc_connection_resume(connection);
  49
+	
  50
+	xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
  51
+	xpc_dictionary_set_string(message, "id", "launch_task");
  52
+	
  53
+	if( path )
  54
+		xpc_dictionary_set_string(message, "path", [path fileSystemRepresentation]);
  55
+	
  56
+	xpc_object_t array = xpc_array_create(NULL, 0);
  57
+	for (id argument in arguments) {
  58
+		xpc_array_append_value(array, xpc_string_create([argument UTF8String]));
  59
+	}
  60
+	
  61
+	xpc_dictionary_set_value(message, "arguments", array);
  62
+	
  63
+	xpc_object_t response = xpc_connection_send_message_with_reply_sync(connection, message);
  64
+	xpc_type_t type = xpc_get_type(response);
  65
+	BOOL success = (type == XPC_TYPE_DICTIONARY);
  66
+	
  67
+	if (!success) {
  68
+		NSLog(@"XPC launch error");
  69
+	}
  70
+}
  71
+
  72
+@end
29  SandboxService.plist
... ...
@@ -0,0 +1,29 @@
  1
+<?xml version="1.0" encoding="UTF-8"?>
  2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  3
+<plist version="1.0">
  4
+<dict>
  5
+	<key>CFBundleDevelopmentRegion</key>
  6
+	<string>English</string>
  7
+	<key>CFBundleExecutable</key>
  8
+	<string>${EXECUTABLE_NAME}</string>
  9
+	<key>CFBundleIdentifier</key>
  10
+	<string>${EXECUTABLE_NAME}</string>
  11
+	<key>CFBundleInfoDictionaryVersion</key>
  12
+	<string>6.0</string>
  13
+	<key>CFBundleName</key>
  14
+	<string>${PRODUCT_NAME}</string>
  15
+	<key>CFBundlePackageType</key>
  16
+	<string>XPC!</string>
  17
+	<key>CFBundleShortVersionString</key>
  18
+	<string>1.0</string>
  19
+	<key>CFBundleSignature</key>
  20
+	<string>????</string>
  21
+	<key>CFBundleVersion</key>
  22
+	<string>1</string>
  23
+	<key>XPCService</key>
  24
+	<dict>
  25
+		<key>ServiceType</key>
  26
+		<string>Application</string>
  27
+	</dict>
  28
+</dict>
  29
+</plist>
165  Sparkle.xcodeproj/project.pbxproj
@@ -131,6 +131,20 @@
131 131
 		61F83F720DBFE140006FDD30 /* SUBasicUpdateDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = 61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */; };
132 132
 		61F83F740DBFE141006FDD30 /* SUBasicUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */; settings = {ATTRIBUTES = (); }; };
133 133
 		61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Sparkle.framework */; settings = {ATTRIBUTES = (Required, ); }; };
  134
+		8B887B611517F3AC000BB292 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B887B601517F3AC000BB292 /* Foundation.framework */; };
  135
+		8B887B7E1517F888000BB292 /* SUXPC.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B887B7C1517F888000BB292 /* SUXPC.h */; };
  136
+		8B887B7F1517F888000BB292 /* SUXPC.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B887B7D1517F888000BB292 /* SUXPC.m */; };
  137
+		8B887B811517FAA3000BB292 /* SUPlainInstallerInternals.m in Sources */ = {isa = PBXBuildFile; fileRef = 61B5F8E509C4CE3C00B25A18 /* SUPlainInstallerInternals.m */; };
  138
+		8B887B821517FAA4000BB292 /* SUPlainInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 618FA5040DAE8AB80026945C /* SUPlainInstaller.m */; };
  139
+		8B887B831517FAE9000BB292 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55C14F31136EFC2400649790 /* SystemConfiguration.framework */; };
  140
+		8B887B841517FAEC000BB292 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6117796E0D1112E000749C97 /* IOKit.framework */; };
  141
+		8B887B861517FAFE000BB292 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B5F8F609C4CEB300B25A18 /* Security.framework */; };
  142
+		8B887B881517FB1C000BB292 /* SUInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 618FA5000DAE88B40026945C /* SUInstaller.m */; };
  143
+		8B887B891517FB3E000BB292 /* SULog.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C14F05136EF6DB00649790 /* SULog.m */; };
  144
+		8B887B8A1517FB43000BB292 /* SUConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A5F09CA6EB100B7442F /* SUConstants.m */; };
  145
+		8B887B8B1517FB52000BB292 /* SUPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 618FA5210DAE8E8A0026945C /* SUPackageInstaller.m */; };
  146
+		8B887B8C1517FB5D000BB292 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; };
  147
+		8B887B8E15180767000BB292 /* sandbox_service.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B887B8D15180767000BB292 /* sandbox_service.m */; };
134 148
 /* End PBXBuildFile section */
135 149
 
136 150
 /* Begin PBXContainerItemProxy section */
@@ -162,6 +176,13 @@
162 176
 			remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
163 177
 			remoteInfo = Sparkle;
164 178
 		};
  179
+		8B887B731517F472000BB292 /* PBXContainerItemProxy */ = {
  180
+			isa = PBXContainerItemProxy;
  181
+			containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
  182
+			proxyType = 1;
  183
+			remoteGlobalIDString = 8B887B5D1517F3AB000BB292;
  184
+			remoteInfo = Service;
  185
+		};
165 186
 /* End PBXContainerItemProxy section */
166 187
 
167 188
 /* Begin PBXCopyFilesBuildPhase section */
@@ -379,6 +400,15 @@
379 400
 		61F614540E24A12D009F47E7 /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Sparkle.strings; sourceTree = "<group>"; };
380 401
 		61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBasicUpdateDriver.h; sourceTree = "<group>"; };
381 402
 		61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBasicUpdateDriver.m; sourceTree = "<group>"; };
  403
+		8B887B5E1517F3AB000BB292 /* com.andymatuschak.Sparkle.SandboxService.xpc */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = com.andymatuschak.Sparkle.SandboxService.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
  404
+		8B887B601517F3AC000BB292 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
  405
+		8B887B701517F447000BB292 /* ConfigService.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ConfigService.xcconfig; sourceTree = "<group>"; };
  406
+		8B887B711517F447000BB292 /* ConfigServiceDebug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ConfigServiceDebug.xcconfig; sourceTree = "<group>"; };
  407
+		8B887B721517F447000BB292 /* ConfigServiceRelease.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ConfigServiceRelease.xcconfig; sourceTree = "<group>"; };
  408
+		8B887B7C1517F888000BB292 /* SUXPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUXPC.h; sourceTree = SOURCE_ROOT; };
  409
+		8B887B7D1517F888000BB292 /* SUXPC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUXPC.m; sourceTree = SOURCE_ROOT; };
  410
+		8B887B8D15180767000BB292 /* sandbox_service.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = sandbox_service.m; sourceTree = SOURCE_ROOT; };
  411
+		8B887B8F15180773000BB292 /* SandboxService.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = SandboxService.plist; sourceTree = SOURCE_ROOT; };
382 412
 		8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
383 413
 		8DC2EF5B0486A6940098B216 /* Sparkle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Sparkle.framework; sourceTree = BUILT_PRODUCTS_DIR; };
384 414
 		FA1941CA0D94A70100DD942E /* ConfigFrameworkDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigFrameworkDebug.xcconfig; sourceTree = "<group>"; };
@@ -439,6 +469,18 @@
439 469
 			);
440 470
 			runOnlyForDeploymentPostprocessing = 0;
441 471
 		};
  472
+		8B887B5B1517F3AB000BB292 /* Frameworks */ = {
  473
+			isa = PBXFrameworksBuildPhase;
  474
+			buildActionMask = 2147483647;
  475
+			files = (
  476
+				8B887B611517F3AC000BB292 /* Foundation.framework in Frameworks */,
  477
+				8B887B831517FAE9000BB292 /* SystemConfiguration.framework in Frameworks */,
  478
+				8B887B841517FAEC000BB292 /* IOKit.framework in Frameworks */,
  479
+				8B887B8C1517FB5D000BB292 /* AppKit.framework in Frameworks */,
  480
+				8B887B861517FAFE000BB292 /* Security.framework in Frameworks */,
  481
+			);
  482
+			runOnlyForDeploymentPostprocessing = 0;
  483
+		};
442 484
 		8DC2EF560486A6940098B216 /* Frameworks */ = {
443 485
 			isa = PBXFrameworksBuildPhase;
444 486
 			buildActionMask = 2147483647;
@@ -465,6 +507,7 @@
465 507
 				612279D90DB5470200AB99EA /* Sparkle Unit Tests.octest */,
466 508
 				5D06E8D00FD68C7C005AE3F6 /* BinaryDelta */,
467 509
 				55C14BB7136EEF1500649790 /* finish_installation.app */,
  510
+				8B887B5E1517F3AB000BB292 /* com.andymatuschak.Sparkle.SandboxService.xpc */,
468 511
 			);
469 512
 			name = Products;
470 513
 			sourceTree = "<group>";
@@ -478,12 +521,14 @@
478 521
 				55C14BD5136EEFD000649790 /* finish_installation tool */,
479 522
 				6101354A0DD25B7F0049ACDF /* Unarchiving */,
480 523
 				61299B3A09CB056100B7442F /* User Interface */,
  524
+				8B887B621517F3AC000BB292 /* Sandbox Service */,
481 525
 				61B5F8F309C4CE5900B25A18 /* Other Sources */,
482 526
 				089C1665FE841158C02AAC07 /* Framework Resources */,
483 527
 				61227A100DB5484000AB99EA /* Tests */,
484 528
 				61B5F91D09C4CF7F00B25A18 /* Test Application Sources */,
485 529
 				0867D69AFE84028FC02AAC07 /* Apple Frameworks and Libraries */,
486 530
 				FA1941C40D94A6EA00DD942E /* Configurations */,
  531
+				8B887B5F1517F3AB000BB292 /* Frameworks */,
487 532
 				034768DFFF38A50411DB9C8B /* Products */,
488 533
 			);
489 534
 			name = Sparkle;
@@ -698,6 +743,42 @@
698 743
 			name = "Update Control";
699 744
 			sourceTree = "<group>";
700 745
 		};
  746
+		8B887B5F1517F3AB000BB292 /* Frameworks */ = {
  747
+			isa = PBXGroup;
  748
+			children = (
  749
+				8B887B601517F3AC000BB292 /* Foundation.framework */,
  750
+			);
  751
+			name = Frameworks;
  752
+			sourceTree = "<group>";
  753
+		};
  754
+		8B887B621517F3AC000BB292 /* Sandbox Service */ = {
  755
+			isa = PBXGroup;
  756
+			children = (
  757
+				8B887B801517F8B4000BB292 /* Service */,
  758
+				8B887B631517F3AC000BB292 /* Interface */,
  759
+			);
  760
+			name = "Sandbox Service";
  761
+			path = Update;
  762
+			sourceTree = "<group>";
  763
+		};
  764
+		8B887B631517F3AC000BB292 /* Interface */ = {
  765
+			isa = PBXGroup;
  766
+			children = (
  767
+				8B887B7C1517F888000BB292 /* SUXPC.h */,
  768
+				8B887B7D1517F888000BB292 /* SUXPC.m */,
  769
+			);
  770
+			name = Interface;
  771
+			sourceTree = "<group>";
  772
+		};
  773
+		8B887B801517F8B4000BB292 /* Service */ = {
  774
+			isa = PBXGroup;
  775
+			children = (
  776
+				8B887B8D15180767000BB292 /* sandbox_service.m */,
  777
+				8B887B8F15180773000BB292 /* SandboxService.plist */,
  778
+			);
  779
+			name = Service;
  780
+			sourceTree = "<group>";
  781
+		};
701 782
 		FA1941C40D94A6EA00DD942E /* Configurations */ = {
702 783
 			isa = PBXGroup;
703 784
 			children = (
@@ -718,6 +799,9 @@
718 799
 				FA1941CE0D94A70100DD942E /* ConfigRelaunch.xcconfig */,
719 800
 				FA1941D30D94A70100DD942E /* ConfigRelaunchDebug.xcconfig */,
720 801
 				FA1941D40D94A70100DD942E /* ConfigRelaunchRelease.xcconfig */,
  802
+				8B887B701517F447000BB292 /* ConfigService.xcconfig */,
  803
+				8B887B711517F447000BB292 /* ConfigServiceDebug.xcconfig */,
  804
+				8B887B721517F447000BB292 /* ConfigServiceRelease.xcconfig */,
721 805
 				FA3AAF3B1050B273004B3130 /* ConfigUnitTest.xcconfig */,
722 806
 				FA3AAF3A1050B273004B3130 /* ConfigUnitTestDebug.xcconfig */,
723 807
 				FA3AAF391050B273004B3130 /* ConfigUnitTestRelease.xcconfig */,
@@ -772,6 +856,7 @@
772 856
 				55C14F06136EF6DB00649790 /* SULog.h in Headers */,
773 857
 				55C14F0F136EF73600649790 /* finish_installation.pch in Headers */,
774 858
 				6158A1C5137904B300487EC1 /* SUUpdater_Private.h in Headers */,
  859
+				8B887B7E1517F888000BB292 /* SUXPC.h in Headers */,
775 860
 			);
776 861
 			runOnlyForDeploymentPostprocessing = 0;
777 862
 		};
@@ -851,6 +936,23 @@
851 936
 			productReference = 61B5F90209C4CEE200B25A18 /* Sparkle Test App.app */;
852 937
 			productType = "com.apple.product-type.application";
853 938
 		};
  939
+		8B887B5D1517F3AB000BB292 /* sandbox_service */ = {
  940
+			isa = PBXNativeTarget;
  941
+			buildConfigurationList = 8B887B6E1517F3AC000BB292 /* Build configuration list for PBXNativeTarget "sandbox_service" */;
  942
+			buildPhases = (
  943
+				8B887B5A1517F3AB000BB292 /* Sources */,
  944
+				8B887B5B1517F3AB000BB292 /* Frameworks */,
  945
+				8B887B5C1517F3AB000BB292 /* Resources */,
  946
+			);
  947
+			buildRules = (
  948
+			);
  949
+			dependencies = (
  950
+			);
  951
+			name = sandbox_service;
  952
+			productName = Update;
  953
+			productReference = 8B887B5E1517F3AB000BB292 /* com.andymatuschak.Sparkle.SandboxService.xpc */;
  954
+			productType = "com.apple.product-type.bundle";
  955
+		};
854 956
 		8DC2EF4F0486A6940098B216 /* Sparkle */ = {
855 957
 			isa = PBXNativeTarget;
856 958
 			buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Sparkle" */;
@@ -867,6 +969,7 @@
867 969
 			);
868 970
 			dependencies = (
869 971
 				55C14F97136F044100649790 /* PBXTargetDependency */,
  972
+				8B887B741517F472000BB292 /* PBXTargetDependency */,
870 973
 			);
871 974
 			name = Sparkle;
872 975
 			productInstallPath = "$(HOME)/Library/Frameworks";
@@ -936,6 +1039,7 @@
936 1039
 				612279D80DB5470200AB99EA /* Sparkle Unit Tests */,
937 1040
 				5D06E8CF0FD68C7C005AE3F6 /* BinaryDelta */,
938 1041
 				55C14BB6136EEF1500649790 /* finish_installation */,
  1042
+				8B887B5D1517F3AB000BB292 /* sandbox_service */,
939 1043
 			);
940 1044
 		};
941 1045
 /* End PBXProject section */
@@ -969,6 +1073,13 @@
969 1073
 			);
970 1074
 			runOnlyForDeploymentPostprocessing = 0;
971 1075
 		};
  1076
+		8B887B5C1517F3AB000BB292 /* Resources */ = {
  1077
+			isa = PBXResourcesBuildPhase;
  1078
+			buildActionMask = 2147483647;
  1079
+			files = (
  1080
+			);
  1081
+			runOnlyForDeploymentPostprocessing = 0;
  1082
+		};
972 1083
 		8DC2EF520486A6940098B216 /* Resources */ = {
973 1084
 			isa = PBXResourcesBuildPhase;
974 1085
 			buildActionMask = 2147483647;
@@ -1106,6 +1217,20 @@
1106 1217
 			);
1107 1218
 			runOnlyForDeploymentPostprocessing = 0;
1108 1219
 		};
  1220
+		8B887B5A1517F3AB000BB292 /* Sources */ = {
  1221
+			isa = PBXSourcesBuildPhase;
  1222
+			buildActionMask = 2147483647;
  1223
+			files = (
  1224
+				8B887B881517FB1C000BB292 /* SUInstaller.m in Sources */,
  1225
+				8B887B821517FAA4000BB292 /* SUPlainInstaller.m in Sources */,
  1226
+				8B887B811517FAA3000BB292 /* SUPlainInstallerInternals.m in Sources */,
  1227
+				8B887B8B1517FB52000BB292 /* SUPackageInstaller.m in Sources */,
  1228
+				8B887B8A1517FB43000BB292 /* SUConstants.m in Sources */,
  1229
+				8B887B891517FB3E000BB292 /* SULog.m in Sources */,
  1230
+				8B887B8E15180767000BB292 /* sandbox_service.m in Sources */,
  1231
+			);
  1232
+			runOnlyForDeploymentPostprocessing = 0;
  1233
+		};
1109 1234
 		8DC2EF540486A6940098B216 /* Sources */ = {
1110 1235
 			isa = PBXSourcesBuildPhase;
1111 1236
 			buildActionMask = 2147483647;
@@ -1144,6 +1269,7 @@
1144 1269
 				5D06E8ED0FD68CE4005AE3F6 /* SUBinaryDeltaCommon.m in Sources */,
1145 1270
 				5D06E93A0FD69271005AE3F6 /* SUBinaryDeltaUnarchiver.m in Sources */,
1146 1271
 				55C14F07136EF6DB00649790 /* SULog.m in Sources */,
  1272
+				8B887B7F1517F888000BB292 /* SUXPC.m in Sources */,
1147 1273
 			);
1148 1274
 			runOnlyForDeploymentPostprocessing = 0;
1149 1275
 		};
@@ -1170,6 +1296,11 @@
1170 1296
 			target = 8DC2EF4F0486A6940098B216 /* Sparkle */;
1171 1297
 			targetProxy = 61FA528C0E2D9EB200EF58AD /* PBXContainerItemProxy */;
1172 1298
 		};
  1299
+		8B887B741517F472000BB292 /* PBXTargetDependency */ = {
  1300
+			isa = PBXTargetDependency;
  1301
+			target = 8B887B5D1517F3AB000BB292 /* sandbox_service */;
  1302
+			targetProxy = 8B887B731517F472000BB292 /* PBXContainerItemProxy */;
  1303
+		};
1173 1304
 /* End PBXTargetDependency section */
1174 1305
 
1175 1306
 /* Begin PBXVariantGroup section */
@@ -1478,6 +1609,30 @@
1478 1609
 			};
1479 1610
 			name = Release;
1480 1611
 		};
  1612
+		8B887B6B1517F3AC000BB292 /* Debug */ = {
  1613
+			isa = XCBuildConfiguration;
  1614
+			baseConfigurationReference = 8B887B711517F447000BB292 /* ConfigServiceDebug.xcconfig */;
  1615
+			buildSettings = {
  1616
+				INFOPLIST_FILE = SandboxService.plist;
  1617
+			};
  1618
+			name = Debug;
  1619
+		};
  1620
+		8B887B6C1517F3AC000BB292 /* Release */ = {
  1621
+			isa = XCBuildConfiguration;
  1622
+			baseConfigurationReference = 8B887B721517F447000BB292 /* ConfigServiceRelease.xcconfig */;
  1623
+			buildSettings = {
  1624
+				INFOPLIST_FILE = SandboxService.plist;
  1625
+			};
  1626
+			name = Release;
  1627
+		};
  1628
+		8B887B6D1517F3AC000BB292 /* Release (GC dual-mode; 10.5+) */ = {
  1629
+			isa = XCBuildConfiguration;
  1630
+			baseConfigurationReference = 8B887B721517F447000BB292 /* ConfigServiceRelease.xcconfig */;
  1631
+			buildSettings = {
  1632
+				INFOPLIST_FILE = SandboxService.plist;
  1633
+			};
  1634
+			name = "Release (GC dual-mode; 10.5+)";
  1635
+		};
1481 1636
 /* End XCBuildConfiguration section */
1482 1637
 
1483 1638
 /* Begin XCConfigurationList section */
@@ -1541,6 +1696,16 @@
1541 1696
 			defaultConfigurationIsVisible = 0;
1542 1697
 			defaultConfigurationName = Release;
1543 1698
 		};
  1699
+		8B887B6E1517F3AC000BB292 /* Build configuration list for PBXNativeTarget "sandbox_service" */ = {
  1700
+			isa = XCConfigurationList;
  1701
+			buildConfigurations = (
  1702
+				8B887B6B1517F3AC000BB292 /* Debug */,
  1703
+				8B887B6C1517F3AC000BB292 /* Release */,
  1704
+				8B887B6D1517F3AC000BB292 /* Release (GC dual-mode; 10.5+) */,
  1705
+			);
  1706
+			defaultConfigurationIsVisible = 0;
  1707
+			defaultConfigurationName = Release;
  1708
+		};
1544 1709
 /* End XCConfigurationList section */
1545 1710
 	};
1546 1711
 	rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
1  finish_installation.m
@@ -116,6 +116,7 @@ - (void) relaunch
116 116
             appPath = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:executablepath length:strlen(executablepath)];
117 117
         else
118 118
             appPath = [host installationPath];
  119
+		
119 120
         [[NSWorkspace sharedWorkspace] openFile: appPath];
120 121
     }
121 122
 
91  sandbox_service.m
... ...
@@ -0,0 +1,91 @@
  1
+//
  2
+//  main.m
  3
+//  Update
  4
+//
  5
+//  Created by Whitney Young on 3/19/12.
  6
+//  Copyright (c) 2012 FadingRed. All rights reserved.
  7
+//
  8
+
  9
+#include <xpc/xpc.h>
  10
+#include <Foundation/Foundation.h>
  11
+#import "SUPlainInstallerInternals.h"
  12
+
  13
+static void peer_event_handler(xpc_connection_t peer, xpc_object_t event) 
  14
+{
  15
+	xpc_type_t type = xpc_get_type(event);
  16
+	if (type == XPC_TYPE_ERROR) {
  17
+		if (event == XPC_ERROR_CONNECTION_INVALID) {
  18
+			// The client process on the other end of the connection has either
  19
+			// crashed or cancelled the connection. After receiving this error,
  20
+			// the connection is in an invalid state, and you do not need to
  21
+			// call xpc_connection_cancel(). Just tear down any associated state
  22
+			// here.
  23
+		} else if (event == XPC_ERROR_TERMINATION_IMMINENT) {
  24
+			// Handle per-connection termination cleanup.
  25
+		}
  26
+	} else {
  27
+		assert(type == XPC_TYPE_DICTIONARY);
  28
+		// Handle the message.
  29
+		const char *identifier = xpc_dictionary_get_string(event, "id");
  30
+		BOOL copyPath = strcmp(identifier, "copy_path") == 0;
  31
+		BOOL launchTask = strcmp(identifier, "launch_task") == 0;
  32
+		
  33
+		if( copyPath )
  34
+		{
  35
+			const char *src = xpc_dictionary_get_string(event, "source");
  36
+			const char *dst = xpc_dictionary_get_string(event, "destination");
  37
+			const char *tmp = xpc_dictionary_get_string(event, "tmp");
  38
+			
  39
+			NSFileManager *manager = [NSFileManager defaultManager];
  40
+			NSString *relaunchPathToCopy = src ? [manager stringWithFileSystemRepresentation:src length:strlen(src)] : nil;
  41
+			NSString *targetPath = dst ? [manager stringWithFileSystemRepresentation:dst length:strlen(dst)] : nil;
  42
+			NSString *temporaryName = tmp ? [NSString stringWithUTF8String:tmp] : nil;
  43
+			NSError *error = nil;
  44
+			[SUPlainInstaller copyPathWithAuthentication: relaunchPathToCopy overPath: targetPath temporaryName: temporaryName error: &error];
  45
+			
  46
+			// send response to indicate ok
  47
+			xpc_object_t reply = xpc_dictionary_create_reply(event);
  48
+			xpc_connection_send_message(peer, reply);
  49
+		}
  50
+		else if( launchTask )
  51
+		{
  52
+			const char *path = xpc_dictionary_get_string(event, "path");
  53
+			xpc_object_t array = xpc_dictionary_get_value(event, "arguments");
  54
+
  55
+			NSFileManager *manager = [NSFileManager defaultManager];
  56
+			NSString *relaunchToolPath = path ? [manager stringWithFileSystemRepresentation:path length:strlen(path)] : nil;;
  57
+			NSMutableArray *arguments = [NSMutableArray array];
  58
+			
  59
+			for (size_t i = 0; i < xpc_array_get_count(array); i++) {
  60
+				[arguments addObject:
  61
+				 [NSString stringWithUTF8String:xpc_array_get_string(array, i)]];
  62
+			}
  63
+			
  64
+			[NSTask launchedTaskWithLaunchPath: relaunchToolPath arguments:arguments];
  65
+			
  66
+			// send response to indicate ok
  67
+			xpc_object_t reply = xpc_dictionary_create_reply(event);
  68
+			xpc_connection_send_message(peer, reply);
  69
+		}
  70
+	}
  71
+}
  72
+
  73
+static void event_handler(xpc_connection_t peer) 
  74
+{
  75
+	// By defaults, new connections will target the default dispatch
  76
+	// concurrent queue.
  77
+	xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
  78
+		peer_event_handler(peer, event);
  79
+	});
  80
+	
  81
+	// This will tell the connection to begin listening for events. If you
  82
+	// have some other initialization that must be done asynchronously, then
  83
+	// you can defer this call until after that initialization is done.
  84
+	xpc_connection_resume(peer);
  85
+}
  86
+
  87
+int main(int argc, const char *argv[])
  88
+{
  89
+	xpc_main(event_handler);
  90
+	return 0;
  91
+}
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.