-
Notifications
You must be signed in to change notification settings - Fork 128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Renames #149
Renames #149
Changes from 22 commits
378e389
04233f8
c74ecf9
7faed9e
221519e
22e785a
ceb4f34
3be9c89
62a3967
747e183
81c0841
5b05c23
52a6dc2
72345d4
5d8ffc9
6779322
0c8394f
e3aa7a6
dab5bf2
3a4428c
178648f
d0634fc
dbf4306
d8a1185
54f765b
ae89c0a
b30ee65
46e0447
8eb2517
1e71daa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -268,21 +268,36 @@ - (RACSignal *)installRequest:(SQRLShipItRequest *)request { | |||
return [[[[self | ||||
prepareAndValidateUpdateBundleURLForRequest:request] | ||||
flattenMap:^(NSURL *updateBundleURL) { | ||||
return [[[[[[[self | ||||
return [[[[self | ||||
acquireTargetBundleURLForRequest:request] | ||||
concat:[self installItemToURL:request.targetBundleURL fromURL:updateBundleURL]] | ||||
concat:[RACSignal return:request.updateBundleURL]] | ||||
concat:[RACSignal return:updateBundleURL]] | ||||
concat:[RACSignal defer:^{ | ||||
return [RACSignal return:self.ownedBundle.temporaryURL]; | ||||
}]] | ||||
flattenMap:^(NSURL *location) { | ||||
return [[[self | ||||
deleteOwnedBundleAtURL:location] | ||||
doError:^(NSError *error) { | ||||
NSLog(@"Couldn't remove owned bundle at location %@, error %@", location, error.sqrl_verboseDescription); | ||||
then:^{ | ||||
if (!request.allowRename) return [RACSignal return:request.targetBundleURL]; | ||||
|
||||
return [self renamedTargetIfNeededWithTargetURL:request.targetBundleURL sourceURL:updateBundleURL]; | ||||
}] | ||||
flattenMap:^(NSURL *newTargetURL) { | ||||
SQRLShipItRequest *updatedRequest = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:newTargetURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:request.launchAfterInstallation allowRename:request.allowRename]; | ||||
return [[[[[[[self | ||||
installItemToURL:request.targetBundleURL fromURL:updateBundleURL] | ||||
then:^{ | ||||
if ([request.targetBundleURL isEqual:newTargetURL]) return [RACSignal empty]; | ||||
|
||||
return [self installItemToURL:newTargetURL fromURL:request.targetBundleURL]; | ||||
}] | ||||
concat:[RACSignal return:request.updateBundleURL]] | ||||
concat:[RACSignal return:updateBundleURL]] | ||||
concat:[RACSignal defer:^{ | ||||
return [RACSignal return:self.ownedBundle.temporaryURL]; | ||||
}]] | ||||
flattenMap:^(NSURL *location) { | ||||
return [[[self | ||||
deleteOwnedBundleAtURL:location] | ||||
doError:^(NSError *error) { | ||||
NSLog(@"Couldn't remove owned bundle at location %@, error %@", location, error.sqrl_verboseDescription); | ||||
}] | ||||
catchTo:[RACSignal empty]]; | ||||
}] | ||||
catchTo:[RACSignal empty]]; | ||||
concat:[RACSignal return:updatedRequest]]; | ||||
}] | ||||
doCompleted:^{ | ||||
self.ownedBundle = nil; | ||||
|
@@ -413,6 +428,36 @@ - (RACSignal *)verifyBundleAtURL:(NSURL *)bundleURL usingSignature:(SQRLCodeSign | |||
|
||||
#pragma mark Installation | ||||
|
||||
/// Check if the target should be renamed and provide the renamed URL. | ||||
/// | ||||
/// targetURL - The URL for the target. Cannot be nil. | ||||
/// sourceURL - The URL for the source. Cannot be nil. | ||||
/// | ||||
/// Returns a signal which will send the URL for the renamed target. If a rename | ||||
/// isn't needed then it will send `targetURL`. | ||||
- (RACSignal *)renamedTargetIfNeededWithTargetURL:(NSURL *)targetURL sourceURL:(NSURL *)sourceURL { | ||||
return [RACSignal defer:^{ | ||||
NSBundle *sourceBundle = [NSBundle bundleWithURL:sourceURL]; | ||||
NSString *targetExecutableName = targetURL.lastPathComponent.stringByDeletingPathExtension; | ||||
NSString *sourceExecutableName = sourceBundle.sqrl_executableName; | ||||
|
||||
// If they're already the same then we're good. | ||||
if ([targetExecutableName isEqual:sourceExecutableName]) { | ||||
return [RACSignal return:targetURL]; | ||||
} | ||||
|
||||
NSString *newAppName = [sourceExecutableName stringByAppendingPathExtension:@"app"]; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the bundle's executable name is more canonical than what the zipped thing is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mdiep could you tie break on this one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know why these would ever be different at this point. It seems a little weird to me that this is even a key in the |
||||
NSURL *newTargetURL = [targetURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:newAppName]; | ||||
|
||||
// If there's already something there then don't rename to it. | ||||
if ([NSFileManager.defaultManager fileExistsAtPath:newTargetURL.path]) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a minor ToC-ToU race, to prevent it I’d like to flip the order of installation and rename. In my head, I’ve been modelling the file system as a datastore whose entries are protected by code sign requirements; entries that don’t exist have no protecting requirement (only BSD and ACL permissions). If we can safely (i.e. don't rename from the current entry to the new one if the new one already exists) the existing bundle to the requested name, the install is then from the update bundle (whose name is irrelevant at that point) to the file system entry of the target bundle, as before. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah so I started down that route originally. The problem we run into is that the
So I think we could end up in a situation where:
Now, we have the same problem if we rename after installation, except that we're less likely to run into a problem at that point (though still possible). But for that matter, I'm not sure how swapping the order solves the potential ToC-ToU race. We still have to check for existence before renaming, regardless of when the rename happens. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That’s a great point, I hadn’t considered that 😢
It would be handled by instead of doing a My concern is renaming could be exploited to replace a different app, which the update is named for but doesn’t meet the codesign requirements of. |
||||
return [RACSignal return:targetURL]; | ||||
} | ||||
|
||||
return [RACSignal return:newTargetURL]; | ||||
}]; | ||||
} | ||||
|
||||
- (RACSignal *)installItemToURL:(NSURL *)targetURL fromURL:(NSURL *)sourceURL { | ||||
NSParameterAssert(targetURL != nil); | ||||
NSParameterAssert(sourceURL != nil); | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,10 +73,12 @@ extern NSString * const SQRLShipItRequestPropertyErrorKey; | |
// installing. Can be nil. | ||
// launchAfterInstallation - Whether the updated application should be launched | ||
// after installation. | ||
// allowRename - Should the target be renamed if it doesn't match | ||
// the update? | ||
// | ||
// Returns a request which can be written to disk for ShipIt to read and | ||
// perform. | ||
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation; | ||
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation allowRename:(BOOL)allowRename; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I’d like to call this Alternatively, we could be even more descriptive and make it a |
||
|
||
// The URL to the downloaded update's app bundle. | ||
@property (nonatomic, copy, readonly) NSURL *updateBundleURL; | ||
|
@@ -93,4 +95,8 @@ extern NSString * const SQRLShipItRequestPropertyErrorKey; | |
// Whether to launch the application after an update is successfully installed. | ||
@property (nonatomic, assign, readonly) BOOL launchAfterInstallation; | ||
|
||
// Whether the app should be renamed if the update has a different name than the | ||
// existing app. | ||
@property (nonatomic, assign, readonly) BOOL allowRename; | ||
|
||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -528,7 +528,11 @@ - (RACSignal *)prepareUpdateForInstallation:(SQRLDownloadedUpdate *)update { | |
return [[[[RACSignal | ||
defer:^{ | ||
NSRunningApplication *currentApplication = NSRunningApplication.currentApplication; | ||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:update.bundle.bundleURL targetBundleURL:currentApplication.bundleURL bundleIdentifier:currentApplication.bundleIdentifier launchAfterInstallation:NO]; | ||
NSBundle *appBundle = [NSBundle bundleWithURL:currentApplication.bundleURL]; | ||
// Only allow a rename if the user hasn't renamed the app. | ||
BOOL allowRename = [appBundle.sqrl_executableName isEqual:currentApplication.bundleURL.lastPathComponent.stringByDeletingPathExtension]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the same logic as above? + // If they're already the same then we're good.
+ if ([targetExecutableName isEqual:sourceExecutableName]) {
+ return [RACSignal return:targetURL];
+ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not quite. This is checking to see if the existing app bundle's name matches its There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, right. 👍 Can you add a comment to that effect? |
||
|
||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:update.bundle.bundleURL targetBundleURL:currentApplication.bundleURL bundleIdentifier:currentApplication.bundleIdentifier launchAfterInstallation:NO allowRename:allowRename]; | ||
return [request writeUsingURL:self.shipItStateURL]; | ||
}] | ||
then:^{ | ||
|
@@ -542,7 +546,7 @@ - (RACSignal *)relaunchToInstallUpdate { | |
return [[[[[[[[SQRLShipItRequest | ||
readUsingURL:self.shipItStateURL] | ||
map:^(SQRLShipItRequest *request) { | ||
return [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:request.targetBundleURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:YES]; | ||
return [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:request.targetBundleURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:YES allowRename:request.allowRename]; | ||
}] | ||
flattenMap:^(SQRLShipItRequest *request) { | ||
return [[request | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly should this compare last components rather than executable names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above. The plist is more canonical than whatever the name of the zipped thing is, I think.