15
15
16
16
#include " AppKitPrevention.h"
17
17
18
+ @interface SUDiskImageUnarchiver () <NSFileManagerDelegate >
19
+ @end
20
+
18
21
@implementation SUDiskImageUnarchiver
19
22
{
20
23
NSString *_archivePath;
21
24
NSString *_decryptionPassword;
22
25
NSString *_extractionDirectory;
26
+
27
+ SUUnarchiverNotifier *_notifier;
28
+ double _currentExtractionProgress;
29
+ double _fileProgressIncrement;
23
30
}
24
31
25
32
+ (BOOL )canUnarchivePath : (NSString *)path
@@ -51,6 +58,25 @@ - (void)unarchiveWithCompletionBlock:(void (^)(NSError * _Nullable))completionBl
51
58
});
52
59
}
53
60
61
+ static NSUInteger fileCountForDirectory (NSFileManager *fileManager, NSString *itemPath)
62
+ {
63
+ NSUInteger fileCount = 0 ;
64
+ NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath: itemPath];
65
+ for (NSString * __unused currentFile in dirEnum) {
66
+ fileCount++;
67
+ }
68
+
69
+ return fileCount;
70
+ }
71
+
72
+ - (BOOL )fileManager : (NSFileManager *)fileManager shouldCopyItemAtURL : (NSURL *)srcURL toURL : (NSURL *)dstURL
73
+ {
74
+ _currentExtractionProgress += _fileProgressIncrement;
75
+ [_notifier notifyProgress: _currentExtractionProgress];
76
+
77
+ return YES ;
78
+ }
79
+
54
80
// Called on a non-main thread.
55
81
- (void )extractDMGWithNotifier : (SUUnarchiverNotifier *)notifier SPU_OBJC_DIRECT
56
82
{
@@ -62,13 +88,12 @@ - (void)extractDMGWithNotifier:(SUUnarchiverNotifier *)notifier SPU_OBJC_DIRECT
62
88
NSFileManager *manager;
63
89
NSError *error = nil ;
64
90
NSArray *contents = nil ;
65
- do
66
- {
91
+ do {
67
92
NSString *uuidString = [[NSUUID UUID ] UUIDString ];
68
93
mountPoint = [@" /Volumes" stringByAppendingPathComponent: uuidString];
69
- }
94
+ }
70
95
// Note: this check does not follow symbolic links, which is what we want
71
- while ([[NSURL fileURLWithPath: mountPoint] checkResourceIsReachableAndReturnError: NULL ]);
96
+ while ([[NSURL fileURLWithPath: mountPoint] checkResourceIsReachableAndReturnError: NULL ]);
72
97
73
98
NSData *promptData = [NSData dataWithBytes: " yes\n " length: 4 ];
74
99
@@ -92,6 +117,11 @@ - (void)extractDMGWithNotifier:(SUUnarchiverNotifier *)notifier SPU_OBJC_DIRECT
92
117
NSData *output = nil ;
93
118
NSInteger taskResult = -1 ;
94
119
120
+ NSURL *mountPointURL = [NSURL fileURLWithPath: mountPoint isDirectory: YES ];
121
+ NSURL *extractionDirectoryURL = [NSURL fileURLWithPath: _extractionDirectory isDirectory: YES ];
122
+ NSMutableArray <NSString *> *itemsToExtract = [NSMutableArray array ];
123
+ NSUInteger totalFileExtractionCount = 0 ;
124
+
95
125
{
96
126
NSTask *task = [[NSTask alloc ] init ];
97
127
task.launchPath = @" /usr/bin/hdiutil" ;
@@ -122,8 +152,6 @@ - (void)extractDMGWithNotifier:(SUUnarchiverNotifier *)notifier SPU_OBJC_DIRECT
122
152
goto reportError;
123
153
}
124
154
125
- [notifier notifyProgress: 0.125 ];
126
-
127
155
if (@available (macOS 10.15 , *)) {
128
156
if (![inputPipe.fileHandleForWriting writeData: promptData error: &error]) {
129
157
goto reportError;
@@ -147,50 +175,102 @@ - (void)extractDMGWithNotifier:(SUUnarchiverNotifier *)notifier SPU_OBJC_DIRECT
147
175
148
176
taskResult = task.terminationStatus ;
149
177
}
150
-
151
- [notifier notifyProgress: 0.5 ];
152
178
153
- if (taskResult != 0 )
154
- {
179
+ if (taskResult != 0 ) {
155
180
NSString *resultStr = output ? [[NSString alloc ] initWithData: output encoding: NSUTF8StringEncoding] : nil ;
156
181
SULog (SULogLevelError, @" hdiutil failed with code: %ld data: <<%@ >>" , (long )taskResult, resultStr);
157
182
goto reportError;
158
183
}
159
184
mountedSuccessfully = YES ;
160
185
186
+ // Mounting can take some time, so increment progress
187
+ _currentExtractionProgress = 0.1 ;
188
+ [notifier notifyProgress: _currentExtractionProgress];
189
+
161
190
// Now that we've mounted it, we need to copy out its contents.
162
191
manager = [[NSFileManager alloc ] init ];
163
192
contents = [manager contentsOfDirectoryAtPath: mountPoint error: &error];
164
- if (contents == nil )
165
- {
193
+ if (contents == nil ) {
166
194
SULog (SULogLevelError, @" Couldn't enumerate contents of archive mounted at %@ : %@ " , mountPoint, error);
167
195
goto reportError;
168
196
}
169
-
170
- double itemsCopied = 0 ;
171
- double totalItems = (double )[contents count ];
172
-
173
- for (NSString *item in contents)
174
- {
175
- NSString *fromPath = [mountPoint stringByAppendingPathComponent: item];
176
- NSString *toPath = [_extractionDirectory stringByAppendingPathComponent: item];
197
+
198
+ // Sparkle can support installing pkg files, app bundles, and other bundle types for plug-ins
199
+ // We must not filter any of those out
200
+ for (NSString *item in contents) {
201
+ NSURL *fromPathURL = [mountPointURL URLByAppendingPathComponent: item];
177
202
178
- itemsCopied += 1.0 ;
179
- [notifier notifyProgress: 0.5 + itemsCopied/(totalItems*2.0 )];
203
+ NSString *lastPathComponent = fromPathURL.lastPathComponent ;
180
204
181
- // We skip any files in the DMG which are not readable but include the item in the progress
182
- if (![manager isReadableFileAtPath: fromPath ]) {
205
+ // Ignore hidden files
206
+ if ([lastPathComponent hasPrefix: @" . " ]) {
183
207
continue ;
184
208
}
185
-
186
- SULog (SULogLevelDefault, @" copyItemAtPath:%@ toPath:%@ " , fromPath, toPath);
187
-
188
- if (![manager copyItemAtPath: fromPath toPath: toPath error: &error])
189
- {
209
+
210
+ // Ignore aliases
211
+ NSNumber *aliasFlag = nil ;
212
+ if ([fromPathURL getResourceValue: &aliasFlag forKey: NSURLIsAliasFileKey error: NULL ] && aliasFlag.boolValue ) {
213
+ continue ;
214
+ }
215
+
216
+ // Ignore symbolic links
217
+ NSNumber *symbolicFlag = nil ;
218
+ if ([fromPathURL getResourceValue: &symbolicFlag forKey: NSURLIsSymbolicLinkKey error: NULL ] && symbolicFlag.boolValue ) {
219
+ continue ;
220
+ }
221
+
222
+ // Ensure file is readable
223
+ NSNumber *isReadableFlag = nil ;
224
+ if ([fromPathURL getResourceValue: &isReadableFlag forKey: NSURLIsReadableKey error: NULL ] && !isReadableFlag.boolValue ) {
225
+ continue ;
226
+ }
227
+
228
+ NSNumber *isDirectoryFlag = nil ;
229
+ if (![fromPathURL getResourceValue: &isDirectoryFlag forKey: NSURLIsDirectoryKey error: NULL ]) {
230
+ continue ;
231
+ }
232
+
233
+ BOOL isDirectory = isDirectoryFlag.boolValue ;
234
+ NSString *pathExtension = fromPathURL.pathExtension ;
235
+
236
+ if (isDirectory) {
237
+ // Skip directory types that aren't bundles or regular directories
238
+ if ([pathExtension isEqualToString: @" rtfd" ]) {
239
+ continue ;
240
+ }
241
+ } else {
242
+ // The only non-directory files we care about are (m)pkg files
243
+ if (![pathExtension isEqualToString: @" pkg" ] && ![pathExtension isEqualToString: @" mpkg" ]) {
244
+ continue ;
245
+ }
246
+ }
247
+
248
+ if (isDirectory) {
249
+ totalFileExtractionCount += fileCountForDirectory (manager, fromPathURL.path );
250
+ } else {
251
+ totalFileExtractionCount++;
252
+ }
253
+
254
+ [itemsToExtract addObject: item];
255
+ }
256
+
257
+ _fileProgressIncrement = (0.99 - _currentExtractionProgress) / totalFileExtractionCount;
258
+ _notifier = notifier;
259
+
260
+ // Copy all items we want to extract and notify of progress
261
+ manager.delegate = self;
262
+ for (NSString *item in itemsToExtract) {
263
+ NSURL *fromURL = [mountPointURL URLByAppendingPathComponent: item];
264
+ NSURL *toURL = [extractionDirectoryURL URLByAppendingPathComponent: item];
265
+
266
+ if (![manager copyItemAtURL: fromURL toURL: toURL error: &error]) {
267
+ SULog (SULogLevelError, @" Failed to copy '%@ ' to '%@ ' with error: %@ " , fromURL.path , toURL.path , error);
190
268
goto reportError;
191
269
}
192
270
}
193
271
272
+ [notifier notifyProgress: 1.0 ];
273
+
194
274
[notifier notifySuccess ];
195
275
goto finally;
196
276
0 commit comments