Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 303 lines (254 sloc) 13.629 kB
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
1 //
2 // SUBasicUpdateDriver.m
3 // Sparkle
4 //
5 // Created by Andy Matuschak on 4/23/08.
6 // Copyright 2008 Andy Matuschak. All rights reserved.
7 //
8
9 #import "SUBasicUpdateDriver.h"
10 #import "Sparkle.h"
11
12 @implementation SUBasicUpdateDriver
13
14 - (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb
15 {
16 hostBundle = [hb retain];
cec9166 @mattstevens Improved notification handling for otherwise UI-less background appli…
mattstevens authored
17 isBackgroundApplication = ([[hostBundle objectForInfoDictionaryKey:@"LSUIElement"] doubleValue]);
8b3d15b @andymatuschak Moved the check for whether the host is running from a disk image to …
andymatuschak authored
18
19 if ([hostBundle isRunningFromDiskImage])
20 {
1552d4e @andymatuschak Changed the disk image failure string to "%1$@ can't be updated when …
andymatuschak authored
21 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURunningFromDiskImageError userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:SULocalizedString(@"%1$@ can't be updated when it's running from a disk image. Move %1$@ to your Applications folder, relaunch it from there, and try again.", nil), [hostBundle name]] forKey:NSLocalizedDescriptionKey]]];
8b3d15b @andymatuschak Moved the check for whether the host is running from a disk image to …
andymatuschak authored
22 return;
23 }
24
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
25 SUAppcast *appcast = [[SUAppcast alloc] init];
26 CFRetain(appcast); // We'll manage the appcast's memory ourselves so we don't have to make it an IV to support GC.
27 [appcast release];
28
29 [appcast setDelegate:self];
57c1f3e @andymatuschak Bumped version number to 1.5b4. Siiigh...
andymatuschak authored
30 [appcast setUserAgentString:[NSString stringWithFormat: @"%@/%@ Sparkle/1.5b4", [hostBundle name], [hostBundle displayVersion]]];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
31 [appcast fetchAppcastFromURL:appcastURL];
251706d @andymatuschak Fixed a serious bug which could cause a server to DDoS'd in the case …
andymatuschak authored
32 [[SUUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:SULastCheckTimeKey];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
33 }
34
aaa60d2 @andymatuschak Fixed 244761
andymatuschak authored
35 - (id <SUVersionComparison>)_versionComparator
36 {
37 id <SUVersionComparison> comparator = nil;
38
39 // Give the delegate a chance to provide a custom version comparator
40 if ([delegate respondsToSelector:@selector(versionComparatorForHostBundle:)])
41 comparator = [delegate versionComparatorForHostBundle:hostBundle];
42
43 // If we don't get a comparator from the delegate, use the default comparator
44 if (!comparator)
45 comparator = [SUStandardVersionComparator defaultComparator];
46
47 return comparator;
48 }
49
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
50 - (BOOL)isItemNewer:(SUAppcastItem *)ui
51 {
aaa60d2 @andymatuschak Fixed 244761
andymatuschak authored
52 return [[self _versionComparator] compareVersion:[hostBundle version] toVersion:[ui versionString]] == NSOrderedAscending;
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
53 }
54
55 - (BOOL)hostSupportsItem:(SUAppcastItem *)ui
56 {
57 if ([ui minimumSystemVersion] == nil || [[ui minimumSystemVersion] isEqualToString:@""]) { return YES; }
d60b87d @andymatuschak Modified my fix to 244761 (custom version comparators) slightly to ma…
andymatuschak authored
58 return [[SUStandardVersionComparator defaultComparator] compareVersion:[ui minimumSystemVersion] toVersion:[NSWorkspace systemVersionString]] != NSOrderedDescending;
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
59 }
60
61 - (BOOL)itemContainsSkippedVersion:(SUAppcastItem *)ui
62 {
1264a6b @andymatuschak Sparkle now finds the newest update in an appcast for which the host …
andymatuschak authored
63 NSString *skippedVersion = [[SUUserDefaults standardUserDefaults] objectForKey:SUSkippedVersionKey];
64 if (skippedVersion == nil) { return NO; }
aaa60d2 @andymatuschak Fixed 244761
andymatuschak authored
65 return [[self _versionComparator] compareVersion:[ui versionString] toVersion:skippedVersion] != NSOrderedDescending;
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
66 }
67
68 - (BOOL)itemContainsValidUpdate:(SUAppcastItem *)ui
69 {
70 return [self hostSupportsItem:ui] && [self isItemNewer:ui] && ![self itemContainsSkippedVersion:ui];
71 }
72
73 - (void)appcastDidFinishLoading:(SUAppcast *)ac
74 {
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
75 if ([delegate respondsToSelector:@selector(appcastDidFinishLoading:forHostBundle:)])
76 [delegate appcastDidFinishLoading:ac forHostBundle:hostBundle];
251706d @andymatuschak Fixed a serious bug which could cause a server to DDoS'd in the case …
andymatuschak authored
77
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
78 // Now we have to find the best valid update in the appcast.
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
79 if ([delegate respondsToSelector:@selector(bestValidUpdateInAppcast:forHostBundle:)]) // Does the delegate want to handle it?
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
80 {
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
81 updateItem = [delegate bestValidUpdateInAppcast:ac forHostBundle:hostBundle];
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
82 }
83 else // If not, we'll take care of it ourselves.
84 {
85 // Find the first update we can actually use.
251706d @andymatuschak Fixed a serious bug which could cause a server to DDoS'd in the case …
andymatuschak authored
86 NSEnumerator *updateEnumerator = [[ac items] objectEnumerator];
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
87 do {
88 updateItem = [updateEnumerator nextObject];
89 } while (updateItem && ![self hostSupportsItem:updateItem]);
90
91 [updateItem retain];
92 }
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
93 CFRelease(ac); // Remember that we're explicitly managing the memory of the appcast.
94 if (updateItem == nil) { [self didNotFindUpdate]; return; }
95
96 if ([self itemContainsValidUpdate:updateItem])
97 [self didFindValidUpdate];
98 else
99 [self didNotFindUpdate];
100 }
101
102 - (void)appcast:(SUAppcast *)ac failedToLoadWithError:(NSError *)error
103 {
104 CFRelease(ac); // Remember that we're explicitly managing the memory of the appcast.
105 [self abortUpdateWithError:error];
106 }
107
108 - (void)didFindValidUpdate
109 {
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
110 if ([delegate respondsToSelector:@selector(didFindValidUpdate:toHostBundle:)])
111 [delegate didFindValidUpdate:updateItem toHostBundle:hostBundle];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
112 [self downloadUpdate];
113 }
114
115 - (void)didNotFindUpdate
116 {
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
117 if ([delegate respondsToSelector:@selector(didNotFindUpdateToHostBundle:)])
118 [delegate didNotFindUpdateToHostBundle:hostBundle];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
119 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUNoUpdateError userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:SULocalizedString(@"You already have the newest version of %@.", nil), [hostBundle name]] forKey:NSLocalizedDescriptionKey]]];
120 }
121
122 - (void)downloadUpdate
123 {
124 download = [[NSURLDownload alloc] initWithRequest:[NSURLRequest requestWithURL:[updateItem fileURL]] delegate:self];
125 }
126
127 - (void)download:(NSURLDownload *)d decideDestinationWithSuggestedFilename:(NSString *)name
128 {
129 // If name ends in .txt, the server probably has a stupid MIME configuration. We'll give the developer the benefit of the doubt and chop that off.
130 if ([[name pathExtension] isEqualToString:@"txt"])
131 name = [name stringByDeletingPathExtension];
132
133 // We create a temporary directory in /tmp and stick the file there.
134 // Not using a GUID here because hdiutil for some reason chokes on GUIDs. Too long? I really have no idea.
135 NSString *prefix = [NSString stringWithFormat:@"%@ %@ Update", [hostBundle name], [hostBundle version]];
136 NSString *tempDir = [NSTemporaryDirectory() stringByAppendingPathComponent:prefix];
137 int cnt=1;
138 while ([[NSFileManager defaultManager] fileExistsAtPath:tempDir] && cnt <= 999999)
139 tempDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ %d", prefix, cnt++]];
140 BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:tempDir attributes:nil];
141 if (!success)
142 {
143 // Okay, something's really broken with /tmp
144 [download cancel];
145 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUTemporaryDirectoryError userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Can't make a temporary directory for the update download at %@.",tempDir] forKey:NSLocalizedDescriptionKey]]];
146 }
147
148 downloadPath = [[tempDir stringByAppendingPathComponent:name] retain];
149 [download setDestination:downloadPath allowOverwrite:YES];
150 }
151
152 - (void)downloadDidFinish:(NSURLDownload *)d
153 {
154 [self extractUpdate];
155 }
156
157 - (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
158 {
49f717c @andymatuschak Fixes #230173 by removing partially downloaded files on download error.
andymatuschak authored
159 // Get rid of what we've downloaded so far, if anything.
160 if (downloadPath != nil)
161 [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[downloadPath stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[downloadPath lastPathComponent]] tag:NULL];
8dfd93e @andymatuschak Made the error displayed on download failure human-friendly.
andymatuschak authored
162 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while downloading the update. Please try again later.", nil), NSLocalizedDescriptionKey, [error localizedDescription], NSLocalizedFailureReasonErrorKey, nil]]];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
163 }
164
c9bd132 @andymatuschak Fixes 246469
andymatuschak authored
165 - (BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType
166 {
0af86bb @andymatuschak Hopefully actually fixing 246469: it seems that while Cocoa documents…
andymatuschak authored
167 // We don't want the download system to extract our gzips.
168 // Note that we use a substring matching here instead of direct comparison because the docs say "application/gzip" but the system *uses* "application/x-gzip". This is a documentation bug.
cec9166 @mattstevens Improved notification handling for otherwise UI-less background appli…
mattstevens authored
169 return ([encodingType rangeOfString:@"gzip"].location == NSNotFound);
c9bd132 @andymatuschak Fixes 246469
andymatuschak authored
170 }
171
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
172 - (void)extractUpdate
173 {
174 // DSA verification, if activated by the developer
175 if ([[hostBundle objectForInfoDictionaryKey:SUExpectsDSASignatureKey] boolValue])
176 {
21b4aee @andymatuschak Fixes bug 228454.
andymatuschak authored
177 if (![[NSFileManager defaultManager] validatePath:downloadPath withEncodedDSASignature:[updateItem DSASignature] withPublicDSAKey:[hostBundle publicDSAKey]])
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
178 {
179 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUSignatureError userInfo:[NSDictionary dictionaryWithObject:@"The update is improperly signed." forKey:NSLocalizedDescriptionKey]]];
180 return;
181 }
182 }
183
3daa88d @andymatuschak --fixes lp:242525 --fixes lp:242561 --fixes lp:242564
andymatuschak authored
184 SUUnarchiver *unarchiver = [SUUnarchiver unarchiverForURL:[[[NSURL alloc] initFileURLWithPath:downloadPath] autorelease]];
441a538 @andymatuschak Oops! Fixed a warning that caused Sparkle not to compile. Made the un…
andymatuschak authored
185 if (!unarchiver)
186 {
187 NSLog(@"Sparkle Error: No valid unarchiver for %@!", downloadPath);
188 [self unarchiverDidFail:nil];
764bed3 @andymatuschak Okay, *really* fixing the bug this time. Thanks for watching my back,…
andymatuschak authored
189 return;
441a538 @andymatuschak Oops! Fixed a warning that caused Sparkle not to compile. Made the un…
andymatuschak authored
190 }
3daa88d @andymatuschak --fixes lp:242525 --fixes lp:242561 --fixes lp:242564
andymatuschak authored
191 CFRetain(unarchiver); // Manage this memory manually so we don't have to make it an IV.
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
192 [unarchiver setDelegate:self];
93ea93d @andymatuschak Fixes 236695
andymatuschak authored
193 [unarchiver start];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
194 }
195
196 - (void)unarchiverDidFinish:(SUUnarchiver *)ua
197 {
441a538 @andymatuschak Oops! Fixed a warning that caused Sparkle not to compile. Made the un…
andymatuschak authored
198 if (ua) { CFRelease(ua); }
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
199 [self installUpdate];
200 }
201
202 - (void)unarchiverDidFail:(SUUnarchiver *)ua
203 {
441a538 @andymatuschak Oops! Fixed a warning that caused Sparkle not to compile. Made the un…
andymatuschak authored
204 if (ua) { CFRelease(ua); }
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
205 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUUnarchivingError userInfo:[NSDictionary dictionaryWithObject:SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil) forKey:NSLocalizedDescriptionKey]]];
206 }
207
6d8f5be @andymatuschak Merging in David Smith's branch to fix bug #230123. We now copy the r…
andymatuschak authored
208 - (BOOL)shouldInstallSynchronously { return NO; }
209
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
210 - (void)installUpdate
211 {
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
212 if ([delegate respondsToSelector:@selector(updateWillInstall:toHostBundle:)])
213 [delegate updateWillInstall:updateItem toHostBundle:hostBundle];
6d8f5be @andymatuschak Merging in David Smith's branch to fix bug #230123. We now copy the r…
andymatuschak authored
214 // Copy the relauncher into a temporary directory so we can get to it after the new version's installed.
215 NSString *relaunchPathToCopy = [[NSBundle bundleForClass:[self class]] pathForResource:@"relaunch" ofType:@""];
216 NSString *targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]];
217 // Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems
218 [[NSFileManager defaultManager] removeFileAtPath:targetPath handler:nil];
219 if ([[NSFileManager defaultManager] copyPath:relaunchPathToCopy toPath:targetPath handler:nil])
220 relaunchPath = [targetPath retain];
221
222 [SUInstaller installFromUpdateFolder:[downloadPath stringByDeletingLastPathComponent] overHostBundle:hostBundle delegate:self synchronously:[self shouldInstallSynchronously]];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
223 }
224
225 - (void)installerFinishedForHostBundle:(NSBundle *)hb
226 {
227 if (hb != hostBundle) { return; }
228 [self relaunchHostApp];
229 }
230
231 - (void)relaunchHostApp
232 {
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
233 // Give the host app an opportunity to postpone the relaunch.
234 static BOOL postponedOnce = NO;
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
235 if (!postponedOnce && [delegate respondsToSelector:@selector(shouldPostponeRelaunchForUpdate:toHostBundle:untilInvoking:)])
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
236 {
237 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[[self class] instanceMethodSignatureForSelector:@selector(relaunchHostApp)] retain]];
238 [invocation setSelector:@selector(relaunchHostApp)];
239 [invocation setTarget:self];
240 postponedOnce = YES;
7d8ada5 @andymatuschak Improved delegate names and made SUProbingUpdateDriver use SUUpdater'…
andymatuschak authored
241 if ([delegate shouldPostponeRelaunchForUpdate:updateItem toHostBundle:hostBundle untilInvoking:invocation])
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
242 return;
243 }
46f9b4a @andymatuschak Fixes 242566
andymatuschak authored
244
245 [self cleanUp]; // Clean up the download and extracted files.
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
246
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
247 [[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self];
5c32832 @andymatuschak Fixes bug 228446
andymatuschak authored
248 if ([delegate respondsToSelector:@selector(updaterWillRelaunchApplication)])
249 [delegate updaterWillRelaunchApplication];
250
1c7fb59 @andymatuschak Killed one of the two remaining @try/@catch blocks in my code. I'll g…
andymatuschak authored
251 if(!relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:relaunchPath])
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
252 {
253 // Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin.
1c7fb59 @andymatuschak Killed one of the two remaining @try/@catch blocks in my code. I'll g…
andymatuschak authored
254 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [[NSBundle mainBundle] name]], NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", relaunchPath], NSLocalizedFailureReasonErrorKey, nil]]];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
255 // We intentionally don't abandon the update here so that the host won't initiate another.
1c7fb59 @andymatuschak Killed one of the two remaining @try/@catch blocks in my code. I'll g…
andymatuschak authored
256 return;
257 }
258
259 [NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:[hostBundle bundlePath], [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], nil]];
260
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
261 [NSApp terminate:self];
262 }
263
46f9b4a @andymatuschak Fixes 242566
andymatuschak authored
264 - (void)cleanUp
265 {
266 [[NSFileManager defaultManager] removeFileAtPath:[downloadPath stringByDeletingLastPathComponent] handler:nil];
267 }
268
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
269 - (void)installerForHostBundle:(NSBundle *)hb failedWithError:(NSError *)error
270 {
271 if (hb != hostBundle) { return; }
6d8f5be @andymatuschak Merging in David Smith's branch to fix bug #230123. We now copy the r…
andymatuschak authored
272 [[NSFileManager defaultManager] removeFileAtPath:relaunchPath handler:NULL]; // Clean up the copied relauncher.
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
273 [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil), NSLocalizedDescriptionKey, [error localizedDescription], NSLocalizedFailureReasonErrorKey, nil]]];
274 }
275
276 - (void)abortUpdate
277 {
278 [[NSNotificationCenter defaultCenter] removeObserver:self];
279 [super abortUpdate];
280 }
281
282 - (void)abortUpdateWithError:(NSError *)error
283 {
284 if ([error code] != SUNoUpdateError) // Let's not bother logging this.
285 NSLog(@"Sparkle Error: %@", [error localizedDescription]);
286 if ([error localizedFailureReason])
287 NSLog(@"Sparkle Error (continued): %@", [error localizedFailureReason]);
0a4bc65 @andymatuschak No longer leaking the NSURLDownload of the update archive.
andymatuschak authored
288 if (download)
289 [download cancel];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
290 [self abortUpdate];
291 }
292
293 - (void)dealloc
294 {
295 [hostBundle release];
0a4bc65 @andymatuschak No longer leaking the NSURLDownload of the update archive.
andymatuschak authored
296 [download release];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
297 [downloadPath release];
6d8f5be @andymatuschak Merging in David Smith's branch to fix bug #230123. We now copy the r…
andymatuschak authored
298 [relaunchPath release];
bc3be9a Touched practically every line of code in a super-monster-awesome ref…
andym authored
299 [super dealloc];
300 }
301
302 @end
Something went wrong with that request. Please try again.