diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index 428329dd..f5efef3f 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -148,8 +148,8 @@ 4C6F228C19A4AA700012310C /* MPAutotypeDelay.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C6F228B19A4AA700012310C /* MPAutotypeDelay.m */; }; 4C701CBC178618A000581B88 /* 12_RemoteTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 4C701CBB178618A000581B88 /* 12_RemoteTemplate.pdf */; }; 4C7155D81A10DB6D00979307 /* IconSelection.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C7155DA1A10DB6D00979307 /* IconSelection.xib */; }; - 4C71BCB42167B75900B4CBDA /* MPTestPluginVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C71BCB32167B75900B4CBDA /* MPTestPluginVersion.m */; }; - 4C71BCB72167B79C00B4CBDA /* MPPluginVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C71BCB62167B79C00B4CBDA /* MPPluginVersion.m */; }; + 4C71BCB42167B75900B4CBDA /* MPTestPluginVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C71BCB32167B75900B4CBDA /* MPTestPluginVersionComparator.m */; }; + 4C71BCB72167B79C00B4CBDA /* MPPluginVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C71BCB62167B79C00B4CBDA /* MPPluginVersionComparator.m */; }; 4C735FC02035FCBF00708D53 /* MPPluginEntryActionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C735FBF2035FCBF00708D53 /* MPPluginEntryActionContext.m */; }; 4C73B6F1215E64A7009787F7 /* MPWelcomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C73B6EF215E64A7009787F7 /* MPWelcomeViewController.m */; }; 4C76155C1764C04C0015A1A6 /* GeneralSettings.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C76155E1764C04C0015A1A6 /* GeneralSettings.xib */; }; @@ -578,9 +578,9 @@ 4C7155E81A10DB7700979307 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/IconSelection.strings; sourceTree = ""; }; 4C7155EA1A10DB7800979307 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/IconSelection.strings; sourceTree = ""; }; 4C7155EC1A10DB7900979307 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/IconSelection.strings; sourceTree = ""; }; - 4C71BCB32167B75900B4CBDA /* MPTestPluginVersion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPTestPluginVersion.m; sourceTree = ""; }; - 4C71BCB52167B79C00B4CBDA /* MPPluginVersion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPluginVersion.h; sourceTree = ""; }; - 4C71BCB62167B79C00B4CBDA /* MPPluginVersion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPluginVersion.m; sourceTree = ""; }; + 4C71BCB32167B75900B4CBDA /* MPTestPluginVersionComparator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPTestPluginVersionComparator.m; sourceTree = ""; }; + 4C71BCB52167B79C00B4CBDA /* MPPluginVersionComparator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPluginVersionComparator.h; sourceTree = ""; }; + 4C71BCB62167B79C00B4CBDA /* MPPluginVersionComparator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPluginVersionComparator.m; sourceTree = ""; }; 4C735FBE2035FCBF00708D53 /* MPPluginEntryActionContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPluginEntryActionContext.h; sourceTree = ""; }; 4C735FBF2035FCBF00708D53 /* MPPluginEntryActionContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPluginEntryActionContext.m; sourceTree = ""; }; 4C73B6EE215E64A7009787F7 /* MPWelcomeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPWelcomeViewController.h; sourceTree = ""; }; @@ -1207,7 +1207,7 @@ 4C6BC65F1A36717E00BDDF3D /* MPDatabaseSearch.m */, 4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */, 4C8F0C721FCF1B7A00BE157F /* MPTestPickcharsParser.m */, - 4C71BCB32167B75900B4CBDA /* MPTestPluginVersion.m */, + 4C71BCB32167B75900B4CBDA /* MPTestPluginVersionComparator.m */, 4C45FB1F178E09ED0010007D /* Supporting Files */, ); path = MacPassTests; @@ -1684,8 +1684,8 @@ 4C81867C216664C70068DAFB /* MPPluginRepositoryItemVersionInfo.m */, 4C735FBE2035FCBF00708D53 /* MPPluginEntryActionContext.h */, 4C735FBF2035FCBF00708D53 /* MPPluginEntryActionContext.m */, - 4C71BCB52167B79C00B4CBDA /* MPPluginVersion.h */, - 4C71BCB62167B79C00B4CBDA /* MPPluginVersion.m */, + 4C71BCB52167B79C00B4CBDA /* MPPluginVersionComparator.h */, + 4C71BCB62167B79C00B4CBDA /* MPPluginVersionComparator.m */, ); name = Plugin; path = MacPass; @@ -1935,7 +1935,7 @@ files = ( 4C45FB2D178E0BCB0010007D /* MPDatabaseLoading.m in Sources */, 4C8DEAA21C314D2C00D24C32 /* MPTestAutotypeDelay.m in Sources */, - 4C71BCB42167B75900B4CBDA /* MPTestPluginVersion.m in Sources */, + 4C71BCB42167B75900B4CBDA /* MPTestPluginVersionComparator.m in Sources */, 4C8F0C731FCF1B7A00BE157F /* MPTestPickcharsParser.m in Sources */, 4C45FB30178E0CE20010007D /* MPTestDocument.m in Sources */, 4C6BC6601A36717E00BDDF3D /* MPDatabaseSearch.m in Sources */, @@ -2059,7 +2059,7 @@ 4C1FA07B18231900003A3F8C /* MPDocument+Autotype.m in Sources */, 4C4B7EE917A45EC6000234C7 /* MPDatePickingViewController.m in Sources */, 4C4B7EEE17A467E1000234C7 /* MPGroupInspectorViewController.m in Sources */, - 4C71BCB72167B79C00B4CBDA /* MPPluginVersion.m in Sources */, + 4C71BCB72167B79C00B4CBDA /* MPPluginVersionComparator.m in Sources */, 4C7B63721C0CB51F00D7038C /* TTTCryptographyTransformers.m in Sources */, 4C4B7EF317A467FC000234C7 /* MPEntryInspectorViewController.m in Sources */, 4C1BDF2B1E4392640012A3F0 /* MPPluginDataViewController.m in Sources */, diff --git a/MacPass/Base.lproj/PluginSettings.xib b/MacPass/Base.lproj/PluginSettings.xib index d0caeb0b..25565a0b 100644 --- a/MacPass/Base.lproj/PluginSettings.xib +++ b/MacPass/Base.lproj/PluginSettings.xib @@ -1,9 +1,8 @@ - + - - + @@ -12,6 +11,7 @@ + @@ -21,19 +21,19 @@ - + - + - + - + - + @@ -43,7 +43,7 @@ - + @@ -65,7 +65,7 @@ - + If enabled, Plugins without proper signatures will be allowed to load. Keep in mind, that Plugins have full access to your data! Changes take affect on restart. @@ -74,13 +74,13 @@ - + - + - + @@ -154,7 +154,7 @@ - + @@ -167,7 +167,7 @@ + + @@ -192,13 +200,15 @@ + - + + - + diff --git a/MacPass/MPPlugin.h b/MacPass/MPPlugin.h index 4722c9fc..f0cd7d3f 100644 --- a/MacPass/MPPlugin.h +++ b/MacPass/MPPlugin.h @@ -28,7 +28,6 @@ NS_ASSUME_NONNULL_BEGIN @class KPKEntry; @class KPKAttribute; @class KPKTree; -@class MPPluginVersion; FOUNDATION_EXPORT NSString *const MPPluginUnkownVersion; @@ -36,8 +35,7 @@ FOUNDATION_EXPORT NSString *const MPPluginUnkownVersion; @property (copy, readonly) NSString *identifier; @property (copy, readonly) NSString *name; -@property (nonatomic, copy, readonly, nullable) NSString *humanVersionString; -@property (nonatomic, copy, readonly, nullable) MPPluginVersion *version; +@property (nonatomic, copy, readonly, nullable) NSString *shortVersionString; @property (nonatomic, copy, readonly) NSString *versionString; @property (nonatomic, strong, readonly) NSBundle *bundle; @@ -89,21 +87,12 @@ FOUNDATION_EXPORT NSString *const MPPluginUnkownVersion; - (NSString *)initialValueForAttributeWithKey:(NSString *)key; @end -@protocol MPExportPluginViewController +@protocol MPImportPlugin @required -@property (nonatomic, copy) NSDictionary *exportOptions; +@property (readonly, copy) NSArray *supportedUTIs; +- (KPKTree *)importTreeWithContentsOfURL:(NSURL *)url; @end -@protocol MPExportPlugin -@required -/* Ideally supply a list of Formats supported. This format specifier is used when being called */ -@property (nonatomic, copy) NSDictionary* localizedSuportedFormats; -- (NSData *)dataForTree:(KPKTree *)tree withFormat:(NSString *)format options:(NSDictionary *)options; -@optional -- (NSViewController *)exportViewControllerForTree:(KPKTree *)tree withFormat:(NSString *)format; -@end - - @interface MPPlugin (Deprecated) - (instancetype)initWithPluginManager:(id)manager; diff --git a/MacPass/MPPlugin.m b/MacPass/MPPlugin.m index 6bc1081e..1a84dbb3 100644 --- a/MacPass/MPPlugin.m +++ b/MacPass/MPPlugin.m @@ -25,14 +25,13 @@ #import "MPPluginHost.h" #import "MPSettingsHelper.h" #import "MPPluginConstants.h" -#import "MPPluginVersion.h" +#import "MPPluginVersionComparator.h" NSString *const MPPluginUnkownVersion = @"unkown.plugin.version"; @implementation MPPlugin @synthesize bundle = _bundle; -@synthesize version = _version; - (instancetype)initWithPluginHost:(MPPluginHost *)host { self = [super init]; @@ -70,21 +69,13 @@ NSString *const MPPluginUnkownVersion = @"unkown.plugin.version"; return nil == name ? @"Unkown Plugin" : name; } -- (NSString *)humanVersionString { +- (NSString *)shortVersionString { return self.bundle.infoDictionary[@"CFBundleShortVersionString"]; } -- (MPPluginVersion *)version { - if(!_versionInitialized) { - _version = [MPPluginVersion versionWithVersionString:self.humanVersionString]; - _versionInitialized = YES; - } - return _version; -} - - (NSString *)versionString { if(self.bundle) { - NSString *humanVersion = self.humanVersionString; + NSString *humanVersion = self.shortVersionString; NSString *version = self.bundle.infoDictionary[(NSString *)kCFBundleVersionKey]; if(humanVersion && version) { return [NSString stringWithFormat:@"%@ (%@)", humanVersion, version]; diff --git a/MacPass/MPPluginHost.h b/MacPass/MPPluginHost.h index 1aabdec8..96a5202e 100644 --- a/MacPass/MPPluginHost.h +++ b/MacPass/MPPluginHost.h @@ -31,14 +31,14 @@ FOUNDATION_EXPORT NSString *const MPPluginHostPluginBundleIdentifiyerKey; @class MPPlugin; @class KPKEntry; -@class MPPluginVersion; +@protocol MPImportPlugin; @interface MPPluginHost : NSObject /* List of all plugins known to the plugin manager. Disabled plugins are also present! */ @property (readonly, copy) NSArray *plugins; -@property (nonatomic, readonly) BOOL loadUnsecurePlugins; -@property (readonly, copy) MPPluginVersion *version; +@property (readonly, copy) NSArray *importPlugins; +@property (nonatomic, readonly, copy) NSString *version; + (instancetype)sharedHost; diff --git a/MacPass/MPPluginHost.m b/MacPass/MPPluginHost.m index 75dd024c..6fb611a4 100644 --- a/MacPass/MPPluginHost.m +++ b/MacPass/MPPluginHost.m @@ -28,7 +28,7 @@ #import "MPPluginEntryActionContext.h" #import "MPPluginRepository.h" #import "MPPluginRepositoryItem.h" -#import "MPPluginVersion.h" +#import "MPPluginVersionComparator.h" #import "NSApplication+MPAdditions.h" #import "MPSettingsHelper.h" @@ -48,10 +48,6 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun @property (strong) NSMutableArray *entryActionPluginIdentifiers; @property (strong) NSMutableArray *customAttributePluginIdentifiers; - -@property (nonatomic) BOOL loadUnsecurePlugins; -@property (copy) NSArray *disabledPlugins; - @end @implementation MPPluginHost @@ -77,28 +73,27 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun self = [super init]; if(self) { _mutablePlugins = [[NSMutableArray alloc] init]; - _disabledPlugins = [NSUserDefaults.standardUserDefaults arrayForKey:kMPSettingsKeyLoadUnsecurePlugins]; - _loadUnsecurePlugins = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyLoadUnsecurePlugins]; _entryActionPluginIdentifiers = [[NSMutableArray alloc] init]; _customAttributePluginIdentifiers = [[NSMutableArray alloc] init]; - _version = [[MPPluginVersion alloc] initWithVersionString:NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"]]; - - [self bind:NSStringFromSelector(@selector(loadUnsecurePlugins)) - toObject:NSUserDefaultsController.sharedUserDefaultsController - withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyLoadUnsecurePlugins] - options:nil]; - [self bind:NSStringFromSelector(@selector(disabledPlugins)) - toObject:NSUserDefaultsController.sharedUserDefaultsController - withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyDisabledPlugins] - options:nil]; } return self; } +- (NSString *)version { + return NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"]; +} + - (NSArray *)plugins { return [self.mutablePlugins copy]; } +- (NSArray *)importPlugins { + NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + return ([evaluatedObject conformsToProtocol:@protocol(MPImportPlugin)]); + }]; + return [self.mutablePlugins filteredArrayUsingPredicate:filterPredicate]; +} + - (BOOL)installPluginAtURL:(NSURL *)url error:(NSError *__autoreleasing *)error { if(![self _isValidPluginURL:url]) { if(error) { @@ -120,9 +115,25 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun } - (void)disablePlugin:(MPPlugin *)plugin { + if(plugin.bundle.bundleIdentifier.length == 0) { + return; + } + NSArray *disabledPlugins = [NSUserDefaults.standardUserDefaults arrayForKey:kMPSettingsKeyDisabledPlugins]; + if(NSNotFound == [disabledPlugins indexOfObject:plugin.bundle.bundleIdentifier]) { + disabledPlugins = [disabledPlugins arrayByAddingObject:plugin.bundle.bundleIdentifier]; + [NSUserDefaults.standardUserDefaults setObject:disabledPlugins forKey:kMPSettingsKeyDisabledPlugins]; + } } - (void)enablePlugin:(MPPlugin *)plugin { + if(plugin.bundle.bundleIdentifier.length == 0) { + return; + } + NSArray *disabledPlugins = [NSUserDefaults.standardUserDefaults arrayForKey:kMPSettingsKeyDisabledPlugins]; + disabledPlugins = [disabledPlugins filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + return ![evaluatedObject isEqualToString:plugin.bundle.bundleIdentifier]; + }]]; + [NSUserDefaults.standardUserDefaults setValue:disabledPlugins forKey:kMPSettingsKeyDisabledPlugins]; } @@ -133,20 +144,34 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun } } return nil; - + } #pragma mark - Plugin Loading - (void)loadPlugins { - [MPPluginRepository.defaultRepository fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self _loadPlugins:availablePlugins]; - }); - }]; + if(MPPluginRepository.defaultRepository.isInitialized) { + [self _loadPlugins]; + } + else { + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(_didUpdateAvailablePlugins:) + name:MPPluginRepositoryDidUpdateAvailablePluginsNotification + object:MPPluginRepository.defaultRepository]; + } } -- (void)_loadPlugins:(NSArray *)availablePlugins { +- (void)_didUpdateAvailablePlugins:(NSNotification *)notification { + [NSNotificationCenter.defaultCenter removeObserver:self + name:MPPluginRepositoryDidUpdateAvailablePluginsNotification + object:MPPluginRepository.defaultRepository]; + /* load plugins on the main thread! */ + dispatch_async(dispatch_get_main_queue(), ^{ + [self _loadPlugins]; + }); +} + +- (void)_loadPlugins { NSURL *appSupportDir = [NSApp applicationSupportDirectoryURL:YES]; NSError *error; NSLog(@"Looking for external plugins at %@.", appSupportDir.path); @@ -176,15 +201,20 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun NSLog(@"Skipping %@. No valid plugin file.", pluginURL.path); continue; } - + NSBundle *pluginBundle = [NSBundle bundleWithURL:pluginURL]; if(!pluginBundle) { NSLog(@"Could not access plugin bundle %@", pluginURL.path); continue; } + if([self _isDisabledPluginBundle:pluginBundle]) { + [self _addPluginForBundle:pluginBundle error:NSLocalizedString(@"PLUGIN_ERROR_DISABLED_PLUGIN", "Error for a plugin that is disabled.")]; + } + if(![self _isSignedPluginURL:pluginURL]) { - if(self.loadUnsecurePlugins) { + BOOL loadInsecurePlugins = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyLoadUnsecurePlugins]; + if(loadInsecurePlugins) { NSLog(@"Loading unsecure Plugin at %@.", pluginURL.path); } else { @@ -200,12 +230,13 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun continue; }; - if([self _validateUniqueBundle:pluginBundle]) { + if(![self _isUniqueBundle:pluginBundle]) { NSLog(@"Plugin %@ already loaded!", pluginBundle.bundleIdentifier); continue; } - if(![self _isCompatiblePluginBundle:pluginBundle avaiablePlugins:availablePlugins ]) { + if(![self _isCompatiblePluginBundle:pluginBundle error:&error]) { + NSLog(@"Plugin %@ is not compatible with host!", pluginBundle.bundleIdentifier); [self _addPluginForBundle:pluginBundle error:NSLocalizedString(@"PLUGIN_ERROR_HOST_VERSION_NOT_SUPPORTED", "Plugin is not with this version of MacPass")]; continue; } @@ -233,7 +264,7 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun else { plugin = [[pluginBundle.principalClass alloc] initWithPluginHost:self]; } - + if(plugin) { NSLog(@"Loaded plugin instance %@", pluginBundle.principalClass); [[NSNotificationCenter defaultCenter] postNotificationName:MPPluginHostWillLoadPlugin @@ -259,19 +290,20 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun [self _addPlugin:plugin]; } -- (BOOL)_validateUniqueBundle:(NSBundle *)bundle { - return ![self pluginWithBundleIdentifier:bundle.bundleIdentifier]; +- (BOOL)_isUniqueBundle:(NSBundle *)bundle { + return (nil == [self pluginWithBundleIdentifier:bundle.bundleIdentifier]); } -- (BOOL)_isCompatiblePluginBundle:(NSBundle *)bundle avaiablePlugins:(NSArray *)availablePlugins { +- (BOOL)_isCompatiblePluginBundle:(NSBundle *)bundle error:(NSError * __autoreleasing *)error { MPPluginRepositoryItem *repoItem; - for(MPPluginRepositoryItem *item in availablePlugins) { + for(MPPluginRepositoryItem *item in MPPluginRepository.defaultRepository.availablePlugins) { if([item.bundleIdentifier isEqualToString:bundle.bundleIdentifier]) { repoItem = item; } } - MPPluginVersion *version = [MPPluginVersion versionWithVersionString:bundle.infoDictionary[@"CFBundleShortVersionString"]]; - return [repoItem isPluginVersionCompatibleWithHost:version]; + BOOL isCompatible = [repoItem isPluginVersionCompatibleWithHost:bundle.infoDictionary[@"CFBundleShortVersionString"]]; + BOOL loadIncompatible = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyLoadIncompatiblePlugins]; + return (loadIncompatible || isCompatible); } - (BOOL)_isValidPluginURL:(NSURL *)url { @@ -325,6 +357,14 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun return NO; } +- (BOOL)_isDisabledPluginBundle:(NSBundle *)bundle { + if(bundle.bundleIdentifier.length == 0) { + return YES; // disbable by default if nothing is known + } + NSArray *disabledPlugins = [NSUserDefaults.standardUserDefaults arrayForKey:kMPSettingsKeyDisabledPlugins]; + return (NSNotFound != [disabledPlugins indexOfObject:bundle.bundleIdentifier]); +} + - (void)_addPlugin:(MPPlugin *)plugin { [self.mutablePlugins addObject:plugin]; if([plugin conformsToProtocol:@protocol(MPEntryActionPlugin)]) { diff --git a/MacPass/MPPluginRepository.h b/MacPass/MPPluginRepository.h index 8a81dac4..e041c369 100644 --- a/MacPass/MPPluginRepository.h +++ b/MacPass/MPPluginRepository.h @@ -24,13 +24,20 @@ NS_ASSUME_NONNULL_BEGIN @import Foundation; +FOUNDATION_EXTERN NSString *const MPPluginRepositoryDidUpdateAvailablePluginsNotification; + @class MPPluginRepositoryItem; @interface MPPluginRepository : NSObject @property (class, strong, readonly) MPPluginRepository *defaultRepository; - -- (void)fetchRepositoryDataCompletionHandler:(void (^)(NSArray *availablePlugins))completionHandler; +@property (readonly) BOOL isInitialized; +/* + this property is set asynchronously, to make sure, you receive valid data, + register to MPPluginRepositoryDidUpdateAvailablePlugsinNotification and access + availablePlugins after you received at least on notification + */ +@property (nonatomic, copy, readonly) NSArray *availablePlugins; @end diff --git a/MacPass/MPPluginRepository.m b/MacPass/MPPluginRepository.m index 6f6fd0ec..53546ba7 100644 --- a/MacPass/MPPluginRepository.m +++ b/MacPass/MPPluginRepository.m @@ -24,12 +24,20 @@ #import "MPConstants.h" #import "MPPluginRepositoryItem.h" +NSString *const MPPluginRepositoryDidUpdateAvailablePluginsNotification = @"com.hicknhack.macpass.MPPluginRepositoryDidInitializeAvailablePluginsNotification"; + @interface MPPluginRepository () +@property (nonatomic, copy) NSArray *availablePlugins; +@property NSTimeInterval lastDataFetchTime; +@property BOOL isInitialized; + @end @implementation MPPluginRepository +@synthesize availablePlugins = _availablePlugins; + + (instancetype)defaultRepository { static MPPluginRepository *instance; static dispatch_once_t onceToken; @@ -39,6 +47,36 @@ return instance; } +- (instancetype)init { + self = [super init]; + if(self) { + _isInitialized = NO; + [self fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { + self.availablePlugins = availablePlugins; + self.isInitialized = YES; + }]; + } + return self; +} + +- (NSArray *)availablePlugins { + /* update cache on every read if it's older than 5 minutes */ + if((NSDate.timeIntervalSinceReferenceDate - self.lastDataFetchTime) > 60*5 ) { + NSLog(@"%@: updating available plugins cache.", self.className); + [self fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { + self.availablePlugins = availablePlugins; + }]; + } + return _availablePlugins; +} + +- (void)setAvailablePlugins:(NSArray *)availablePlugins { + @synchronized (self) { + _availablePlugins = [availablePlugins copy]; + self.lastDataFetchTime = NSDate.timeIntervalSinceReferenceDate; + [NSNotificationCenter.defaultCenter postNotificationName:MPPluginRepositoryDidUpdateAvailablePluginsNotification object:self]; + } +} - (void)fetchRepositoryDataCompletionHandler:(void (^)(NSArray * _Nonnull))completionHandler { NSString *urlString = NSBundle.mainBundle.infoDictionary[MPBundlePluginRepositoryURLKey]; diff --git a/MacPass/MPPluginRepositoryBrowserViewController.m b/MacPass/MPPluginRepositoryBrowserViewController.m index 951126be..7b4766ad 100644 --- a/MacPass/MPPluginRepositoryBrowserViewController.m +++ b/MacPass/MPPluginRepositoryBrowserViewController.m @@ -11,7 +11,7 @@ #import "MPPluginHost.h" #import "MPPluginRepository.h" #import "MPPluginRepositoryItem.h" -#import "MPPluginVersion.h" +#import "MPPluginVersionComparator.h" typedef NS_ENUM(NSUInteger, MPPluginTableColumn) { @@ -60,17 +60,17 @@ typedef NS_ENUM(NSUInteger, MPPluginTableColumn) { view.textField.stringValue = item.name; } else if(column == MPPluginTableColumnCurrentVersion) { - view.textField.stringValue = item.currentVersion.versionString; + view.textField.stringValue = item.currentVersion; } else if(column == MPPluginTableColumnStatus) { MPPlugin *plugin = [MPPluginHost.sharedHost pluginWithBundleIdentifier:item.bundleIdentifier]; if(!plugin) { - switch([plugin.version compare:item.currentVersion]) { + switch([plugin.shortVersionString compare:item.currentVersion]) { case NSOrderedSame: view.textField.stringValue = [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_BROWSER_LATEST_VERSION_INSTALLED", "Status for an up-to-date plugin in the plugin browser")]; break; case NSOrderedAscending: - view.textField.stringValue = [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_BROWSER_NEWER_VERSION_%@_AVAILABLE", "Status for an outdated plugin version in the plugin browser"), item.currentVersion.versionString]; + view.textField.stringValue = [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_BROWSER_NEWER_VERSION_%@_AVAILABLE", "Status for an outdated plugin version in the plugin browser"), item.currentVersion]; break; case NSOrderedDescending: view.textField.stringValue = [NSString stringWithFormat:NSLocalizedString(@"PLUGIN_BROWSER_UNKNOWN_PLUGIN_VERSION", "Status for an unkonw plugin version in the plugin browser")]; @@ -89,12 +89,8 @@ typedef NS_ENUM(NSUInteger, MPPluginTableColumn) { } - (void)_refreshRepository { - [MPPluginRepository.defaultRepository fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { - dispatch_async(dispatch_get_main_queue(), ^{ - self.repositoryItems = availablePlugins; - [self.itemTable reloadData]; - }); - }]; + self.repositoryItems = MPPluginRepository.defaultRepository.availablePlugins; + [self.itemTable reloadData]; } diff --git a/MacPass/MPPluginRepositoryItem.h b/MacPass/MPPluginRepositoryItem.h index ba0f38f9..32a3544b 100644 --- a/MacPass/MPPluginRepositoryItem.h +++ b/MacPass/MPPluginRepositoryItem.h @@ -24,12 +24,12 @@ NS_ASSUME_NONNULL_BEGIN -@class MPPluginVersion; +@class MPPluginVersionComparator; @interface MPPluginRepositoryItem : NSObject @property (copy,readonly, nullable) NSString *name; -@property (copy,readonly, nullable) MPPluginVersion *currentVersion; +@property (copy,readonly, nullable) NSString *currentVersion; @property (copy,readonly, nullable) NSString *descriptionText; @property (copy,readonly, nullable) NSURL *sourceURL; @property (copy,readonly, nullable) NSURL *downloadURL; @@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)pluginItemFromDictionary:(NSDictionary *)dict; - (instancetype)initWithDictionary:(NSDictionary *)dict; -- (BOOL)isPluginVersionCompatibleWithHost:(MPPluginVersion *)pluginVersion; +- (BOOL)isPluginVersionCompatibleWithHost:(NSString *)pluginVersion; @end diff --git a/MacPass/MPPluginRepositoryItem.m b/MacPass/MPPluginRepositoryItem.m index e9feb0f6..67f8f6b1 100644 --- a/MacPass/MPPluginRepositoryItem.m +++ b/MacPass/MPPluginRepositoryItem.m @@ -22,7 +22,7 @@ #import "MPPluginRepositoryItem.h" #import "MPPluginRepositoryItemVersionInfo.h" -#import "MPPluginVersion.h" +#import "MPPluginVersionComparator.h" #import "MPPluginHost.h" #import "NSError+Messages.h" @@ -38,7 +38,7 @@ NSString *const MPPluginItemCompatibiltyKey = @"compatibilty"; @interface MPPluginRepositoryItem () @property (copy) NSString *name; -@property (copy) MPPluginVersion *currentVersion; +@property (copy) NSString *currentVersion; @property (copy) NSString *descriptionText; @property (copy) NSURL *sourceURL; @property (copy) NSURL *downloadURL; @@ -63,7 +63,7 @@ NSString *const MPPluginItemCompatibiltyKey = @"compatibilty"; self.descriptionText = dict[MPPluginItemDescriptionKey]; self.downloadURL = [NSURL URLWithString:dict[MPPluginItemDownloadURLKey]]; self.sourceURL = [NSURL URLWithString:dict[MPPluginItemSourceURLKey]]; - self.currentVersion = [MPPluginVersion versionWithVersionString:dict[MPPluginItemCurrentVersionKey]]; + self.currentVersion = dict[MPPluginItemCurrentVersionKey]; self.bundleIdentifier = dict[MPPluginItemBundleIdentifierKey]; [self _buildVersionInfos:dict[MPPluginItemCompatibiltyKey]]; @@ -76,29 +76,29 @@ NSString *const MPPluginItemCompatibiltyKey = @"compatibilty"; return (self.name.length > 0 && self.downloadURL); } -- (BOOL)isPluginVersionCompatibleWithHost:(MPPluginVersion *)pluginVersion { +- (BOOL)isPluginVersionCompatibleWithHost:(NSString *)pluginVersion { if(!pluginVersion) { return NO; } - MPPluginVersion *hostVersion = MPPluginHost.sharedHost.version; - if(!hostVersion) { + if(!MPPluginHost.sharedHost.version) { return NO; } NSMutableArray *matches = [[NSMutableArray alloc] init]; for(MPPluginRepositoryItemVersionInfo *info in self.compatibilty) { - if(NSOrderedSame == [info.version compare:pluginVersion]) { + if(NSOrderedSame == [MPPluginVersionComparator compareVersion:info.version toVersion:pluginVersion]) { [matches addObject:info]; } } if(matches.count != 1) { + NSLog(@"No unique version match found."); return NO; } MPPluginRepositoryItemVersionInfo *matchingInfo = matches.firstObject; - return [matchingInfo isCompatibleWithHostVersion:hostVersion]; + return [matchingInfo isCompatibleWithHostVersion:MPPluginHost.sharedHost.version]; } - (void)_buildVersionInfos:(NSArray*)infos { diff --git a/MacPass/MPPluginRepositoryItemVersionInfo.h b/MacPass/MPPluginRepositoryItemVersionInfo.h index cda567d1..6f3ab8e5 100644 --- a/MacPass/MPPluginRepositoryItemVersionInfo.h +++ b/MacPass/MPPluginRepositoryItemVersionInfo.h @@ -10,18 +10,16 @@ NS_ASSUME_NONNULL_BEGIN -@class MPPluginVersion; - @interface MPPluginRepositoryItemVersionInfo : NSObject -@property (copy, readonly) MPPluginVersion *version; +@property (copy, readonly) NSString *version; + (instancetype)versionInfoWithDict:(NSDictionary *)dict; - (instancetype)initWithDict:(NSDictionary *)dict NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; -- (BOOL)isCompatibleWithHostVersion:(MPPluginVersion *)hostVersion; +- (BOOL)isCompatibleWithHostVersion:(NSString *)hostVersion; @end diff --git a/MacPass/MPPluginRepositoryItemVersionInfo.m b/MacPass/MPPluginRepositoryItemVersionInfo.m index 217d7328..0795dd1e 100644 --- a/MacPass/MPPluginRepositoryItemVersionInfo.m +++ b/MacPass/MPPluginRepositoryItemVersionInfo.m @@ -7,7 +7,7 @@ // #import "MPPluginRepositoryItemVersionInfo.h" -#import "MPPluginVersion.h" +#import "MPPluginVersionComparator.h" NSString *const MPPluginItemCompatibiltyVersionKey = @"pluginVersion"; NSString *const MPPluginItemCompatibiltyMinimumHostVersionKey = @"minimumHostVersion"; @@ -15,9 +15,9 @@ NSString *const MPPluginItemCompatibiltyMaxiumumHostVersionKey = @"maximumHostVe @interface MPPluginRepositoryItemVersionInfo () -@property (copy) MPPluginVersion *version; -@property (copy) MPPluginVersion *minimumHostVersion; -@property (copy) MPPluginVersion *maxiumHostVersion; +@property (copy) NSString *version; +@property (copy) NSString *minimumHostVersion; +@property (copy) NSString *maxiumHostVersion; @end @@ -30,51 +30,21 @@ NSString *const MPPluginItemCompatibiltyMaxiumumHostVersionKey = @"maximumHostVe - (instancetype)initWithDict:(NSDictionary *)dict { self = [super init]; if(self) { - NSString *versionString = dict[MPPluginItemCompatibiltyVersionKey]; - if(!versionString) { - NSLog(@"Version information is missing required %@ key.", MPPluginItemCompatibiltyVersionKey); - self = nil; - return self; - } - self.version = [MPPluginVersion versionWithVersionString:versionString]; - if(!self.version) { - NSLog(@"Malformed plugin version information: %@.", versionString); - self = nil; - return self; - } - NSString *minimumHostVersionString = dict[MPPluginItemCompatibiltyMinimumHostVersionKey]; - if(!minimumHostVersionString) { - NSLog(@"Version information is missing required %@ key.", MPPluginItemCompatibiltyMinimumHostVersionKey); - self = nil; - return self; - } - self.minimumHostVersion = [MPPluginVersion versionWithVersionString:minimumHostVersionString]; - if(!self.minimumHostVersion) { - NSLog(@"Malformed minimum host version information: %@.", minimumHostVersionString); - self = nil; - return self; - } - NSString *maxiumHostVersionString = dict[MPPluginItemCompatibiltyMaxiumumHostVersionKey]; - if(maxiumHostVersionString) { - self.maxiumHostVersion = [MPPluginVersion versionWithVersionString:maxiumHostVersionString]; - if(!self.maxiumHostVersion) { - NSLog(@"Malformed maxium host version information: %@.", maxiumHostVersionString); - self = nil; - return self; - } - } + self.version = dict[MPPluginItemCompatibiltyVersionKey]; + self.minimumHostVersion = dict[MPPluginItemCompatibiltyMinimumHostVersionKey]; + self.maxiumHostVersion = dict[MPPluginItemCompatibiltyMaxiumumHostVersionKey]; } return self; } -- (BOOL)isCompatibleWithHostVersion:(MPPluginVersion *)hostVersion { - if(NSOrderedDescending == [self.minimumHostVersion compare:hostVersion]) { +- (BOOL)isCompatibleWithHostVersion:(NSString *)hostVersion { + if(NSOrderedDescending == [MPPluginVersionComparator compareVersion:self.minimumHostVersion toVersion:hostVersion]) { return NO; } if(!self.maxiumHostVersion) { return YES; } - return (NSOrderedAscending != [self.maxiumHostVersion compare:hostVersion]); + return (NSOrderedAscending != [MPPluginVersionComparator compareVersion:self.maxiumHostVersion toVersion:hostVersion]); } diff --git a/MacPass/MPPluginSettingsController.m b/MacPass/MPPluginSettingsController.m index 45d749e1..480c24f9 100644 --- a/MacPass/MPPluginSettingsController.m +++ b/MacPass/MPPluginSettingsController.m @@ -42,12 +42,13 @@ typedef NS_ENUM(NSUInteger, MPPluginSegmentType) { @interface MPPluginSettingsController () -@property (weak) IBOutlet NSTableView *pluginTableView; -@property (weak) IBOutlet NSView *settingsView; +@property (strong) IBOutlet NSTableView *pluginTableView; +@property (strong) IBOutlet NSView *settingsView; @property (strong) IBOutlet NSView *fallbackSettingsView; -@property (weak) IBOutlet NSTextField *fallbackDescriptionTextField; -@property (weak) IBOutlet NSButton *loadInsecurePlugsinCheckButton; -@property (weak) IBOutlet NSSegmentedControl *addRemovePluginsControl; +@property (strong) IBOutlet NSTextField *fallbackDescriptionTextField; +@property (strong) IBOutlet NSButton *loadInsecurePlugsinCheckButton; +@property (strong) IBOutlet NSSegmentedControl *addRemovePluginsControl; +@property (strong) IBOutlet NSButton *forceIncompatiblePluginsCheckButton; @end @@ -75,9 +76,13 @@ typedef NS_ENUM(NSUInteger, MPPluginSegmentType) { [self.addRemovePluginsControl setEnabled:NO forSegment:MPRemovePluginSegment]; [self.fallbackSettingsView removeFromSuperview]; [self.loadInsecurePlugsinCheckButton bind:NSValueBinding - toObject:[NSUserDefaultsController sharedUserDefaultsController] + toObject:NSUserDefaultsController.sharedUserDefaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyLoadUnsecurePlugins] options:nil]; + [self.forceIncompatiblePluginsCheckButton bind:NSValueBinding + toObject:NSUserDefaultsController.sharedUserDefaultsController + withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyLoadIncompatiblePlugins] + options:nil]; [self.pluginTableView registerForDraggedTypes:@[(NSString *)kUTTypeFileURL]]; } diff --git a/MacPass/MPPluginVersion.h b/MacPass/MPPluginVersion.h deleted file mode 100644 index 862e3eba..00000000 --- a/MacPass/MPPluginVersion.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// MPPluginVersion.h -// MacPass -// -// Created by Michael Starke on 05.10.18. -// Copyright © 2018 HicknHack Software GmbH. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -FOUNDATION_EXPORT NSString *MPPluginVersionWildcard; - -@interface MPPluginVersion : NSObject - -@property (nonatomic, copy, readonly) NSString *versionString; -@property (nonatomic, copy, readonly) NSString *mayorVersion; -@property (nonatomic, copy, readonly) NSString *minorVersion; -@property (nonatomic, copy, readonly) NSString *patchVersion; - -+ (instancetype)versionWithVersionString:(NSString *)versionString; - -- (instancetype)initWithVersionString:(NSString *)versionString NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -- (NSComparisonResult)compare:(MPPluginVersion *)version; - -@end - -NS_ASSUME_NONNULL_END diff --git a/MacPass/MPPluginVersion.m b/MacPass/MPPluginVersion.m deleted file mode 100644 index 30ca2cfb..00000000 --- a/MacPass/MPPluginVersion.m +++ /dev/null @@ -1,122 +0,0 @@ -// -// MPPluginVersion.m -// MacPass -// -// Created by Michael Starke on 05.10.18. -// Copyright © 2018 HicknHack Software GmbH. All rights reserved. -// - -#import "MPPluginVersion.h" - -NSString *MPPluginVersionWildcard = @"*"; - -@interface MPPluginVersion () - -@property (nonatomic, copy) NSString *versionString; -@property (nonatomic, copy) NSString *mayorVersion; -@property (nonatomic, copy) NSString *minorVersion; -@property (nonatomic, copy) NSString *patchVersion; - -@end - -@implementation MPPluginVersion - -+ (instancetype)versionWithVersionString:(NSString *)versionString { - return [[MPPluginVersion alloc] initWithVersionString:versionString]; -} - -- (instancetype)initWithVersionString:(NSString *)versionString { - self = [super init]; - if(self) { - self.mayorVersion = @"0"; - self.minorVersion = @"0"; - self.patchVersion = @"0"; - - NSArray* components = [versionString componentsSeparatedByString:@"."]; - if(components.count >= 1) { - - NSString *mayorVersion = components[0].length > 0 ? components[0] : @"0"; - if([mayorVersion isEqualToString:MPPluginVersionWildcard]) { - self.mayorVersion = MPPluginVersionWildcard; - self.minorVersion = MPPluginVersionWildcard; - self.patchVersion = MPPluginVersionWildcard; - self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion]; - return self; - } - NSCharacterSet *invalidSet = NSCharacterSet.decimalDigitCharacterSet.invertedSet; - NSRange mayorRange = [mayorVersion rangeOfCharacterFromSet:invalidSet]; - if(mayorRange.location != NSNotFound) { - NSLog(@"Invalid Format for Mayor Version"); - self = nil; - return self; - } - self.mayorVersion = mayorVersion; - - if(components.count >= 2) { - NSString *minorVersion = components[1].length > 0 ? components[1] : @"0"; - if([minorVersion isEqualToString:MPPluginVersionWildcard]) { - self.minorVersion = MPPluginVersionWildcard; - self.patchVersion = MPPluginVersionWildcard; - self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion]; - return self; - } - NSRange minorRange = [minorVersion rangeOfCharacterFromSet:invalidSet]; - if(minorRange.location != NSNotFound) { - NSLog(@"Invalid Format for Minor Version"); - self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion]; - self = nil; - return self; - } - self.minorVersion = minorVersion; - - if(components.count == 3) { - NSString *patchVersion = components[2].length > 0 ? components[2] : @"0"; - if([patchVersion isEqualToString:MPPluginVersionWildcard]) { - self.patchVersion = MPPluginVersionWildcard; - self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion]; - return self; - } - NSRange patchRange = [patchVersion rangeOfCharacterFromSet:invalidSet]; - if(patchRange.location != NSNotFound) { - NSLog(@"Invalid Format for Patch Version"); - self = nil; - return self; - } - self.patchVersion = patchVersion; - } - } - } - self.versionString = [NSString stringWithFormat:@"%@.%@.%@", self.mayorVersion, self.minorVersion, self.patchVersion]; - } - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - return self; -} - -- (NSComparisonResult)compare:(MPPluginVersion *)version { - if([self.versionString isEqualToString:version.versionString]) { - return NSOrderedSame; - } - - NSArray *myVersions = @[self.mayorVersion, self.minorVersion, self.patchVersion]; - NSArray *otherVersions = @[version.mayorVersion, version.minorVersion, version.patchVersion]; - - for(NSUInteger index = 0; index < 3; index++) { - NSString *myVersion = myVersions[index]; - NSString *otherVersion = otherVersions[index]; - - if([myVersion isEqualToString:MPPluginVersionWildcard] || [otherVersion isEqualToString:MPPluginVersionWildcard]) { - return NSOrderedSame; - } - - NSComparisonResult compare = [myVersion compare:otherVersion options:NSNumericSearch|NSCaseInsensitiveSearch]; - if(compare != NSOrderedSame) { - return compare; - } - } - return NSOrderedSame; -} - -@end diff --git a/MacPass/MPPluginVersionComparator.h b/MacPass/MPPluginVersionComparator.h new file mode 100644 index 00000000..1d7008ca --- /dev/null +++ b/MacPass/MPPluginVersionComparator.h @@ -0,0 +1,30 @@ +// +// MPPluginVersion.h +// MacPass +// +// Created by Michael Starke on 05.10.18. +// Copyright © 2018 HicknHack Software GmbH. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSString *MPPluginVersionWildcard; + +@interface MPPluginVersionComparator : NSObject + +typedef NS_ENUM(NSUInteger, MPVersionCharacterType ) { + kMPVersionCharacterTypeWildcard, + kMPVersionCharacterTypeSeparator, + kMPVersionCharacterTypeNumeric, + kMPVersionCharacterTypeString +}; + ++ (MPVersionCharacterType)typeOfCharacter:(NSString *)character; ++ (NSArray *)splitVersionString:(NSString *)versionString; ++ (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MacPass/MPPluginVersionComparator.m b/MacPass/MPPluginVersionComparator.m new file mode 100644 index 00000000..fdbfcc67 --- /dev/null +++ b/MacPass/MPPluginVersionComparator.m @@ -0,0 +1,167 @@ +// +// MPPluginVersion.m +// MacPass +// +// Created by Michael Starke on 05.10.18. +// Copyright © 2018 HicknHack Software GmbH. All rights reserved. +// + +#import "MPPluginVersionComparator.h" + +@implementation MPPluginVersionComparator + + ++ (MPVersionCharacterType)typeOfCharacter:(NSString *)character { + if([character isEqualToString:@"."]) { + return kMPVersionCharacterTypeSeparator; + } + if([character isEqualToString:@"*"]) { + return kMPVersionCharacterTypeWildcard; + } + else if([NSCharacterSet.decimalDigitCharacterSet characterIsMember:[character characterAtIndex:0]]) { + return kMPVersionCharacterTypeNumeric; + } + else if([NSCharacterSet.whitespaceAndNewlineCharacterSet characterIsMember:[character characterAtIndex:0]]) { + return kMPVersionCharacterTypeSeparator; + } + else if([NSCharacterSet.punctuationCharacterSet characterIsMember:[character characterAtIndex:0]]) { + return kMPVersionCharacterTypeSeparator; + } + return kMPVersionCharacterTypeString; +} + ++ (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB { + NSArray* partsA = [self splitVersionString:versionA]; + NSArray* partsB = [self splitVersionString:versionB]; + + + MPVersionCharacterType typeA; + MPVersionCharacterType typeB; + + NSUInteger minPartsCount = MIN(partsA.count, partsB.count); + for(NSUInteger index = 0; index < minPartsCount; index++) { + + NSString *partA = partsA[index]; + NSString *partB = partsB[index]; + + typeA = [self typeOfCharacter:partA]; + typeB = [self typeOfCharacter:partB]; + + if(typeA == typeB) { + if(typeA == kMPVersionCharacterTypeNumeric) { + NSInteger valueA = partA.integerValue; + NSInteger valueB = partB.integerValue; + if(valueA < valueB) { + return NSOrderedAscending; + } + else if (valueA > valueB) { + return NSOrderedDescending; + } + } + else if(typeA == kMPVersionCharacterTypeString) { + NSComparisonResult stringCompare = [partA compare:partB]; + if(stringCompare != NSOrderedSame) { + return stringCompare; + } + } + } + else { + /* no wildcards, direct compare */ + if(typeA != kMPVersionCharacterTypeWildcard && typeB != kMPVersionCharacterTypeWildcard) { + if(typeA != kMPVersionCharacterTypeString && typeB == kMPVersionCharacterTypeString) { + return NSOrderedDescending; + } + else if(typeA == kMPVersionCharacterTypeString && typeB != kMPVersionCharacterTypeString) { + return NSOrderedAscending; + } + else { + if(typeA == kMPVersionCharacterTypeNumeric) { + return NSOrderedDescending; + } + else { + return NSOrderedAscending; + } + } + } + /* we have at least one wildcards */ + else { + /* one is separator, separator wins*/ + if(typeA == kMPVersionCharacterTypeSeparator) { + return NSOrderedDescending; + } + else if(typeB == kMPVersionCharacterTypeSeparator) { + return NSOrderedAscending; + } + /* one is number or string, gets matched by wildcard */ + } + } + } + if(partsA.count != partsB.count) { + NSArray *longerParts; + NSComparisonResult shorterResult; + NSComparisonResult longerResult; + MPVersionCharacterType lastShortType; + if(partsA.count > partsB.count) { + lastShortType = typeB; + longerParts = partsA; + shorterResult = NSOrderedAscending; + longerResult = NSOrderedDescending; + } + else { + lastShortType = typeA; + longerParts = partsB; + shorterResult = NSOrderedDescending; + longerResult = NSOrderedAscending; + + } + /* check if wildcard was last part, then the rest does not matter */ + if(lastShortType == kMPVersionCharacterTypeWildcard) { + return NSOrderedSame; + } + for(NSUInteger longerPartsIndex = minPartsCount; longerPartsIndex < longerParts.count; longerPartsIndex++) { + NSString *part = longerParts[longerPartsIndex]; + MPVersionCharacterType type = [self typeOfCharacter:part]; + /* overhangpart is string, the shorter wins */ + if(type == kMPVersionCharacterTypeString) { + return shorterResult; + } + /* overhangpart is number, if not null, longer wins */ + else if(type == kMPVersionCharacterTypeNumeric) { + if(part.integerValue != 0) { + return longerResult; + } + } + // versions are considered the same as long as 0, separators or wildcards follow + } + } + return NSOrderedSame; +} + + ++ (NSArray *)splitVersionString:(NSString *)versionString { + if(versionString.length == 0) { + return @[]; + } + NSMutableArray *versionSegements = [[NSMutableArray alloc] init]; + NSMutableString *currentSegment = [[versionString substringWithRange:NSMakeRange(0, 1)] mutableCopy]; + MPVersionCharacterType oldType = [self typeOfCharacter:currentSegment]; + + for(NSUInteger characterIndex = 1; characterIndex < versionString.length; characterIndex++) { + NSString *character = [versionString substringWithRange:NSMakeRange(characterIndex, 1)]; + MPVersionCharacterType newType = [self typeOfCharacter:character]; + if(oldType != newType || newType == kMPVersionCharacterTypeSeparator) { + [versionSegements addObject:[currentSegment copy]]; + [currentSegment setString:character]; + } + else { + [currentSegment appendString:character]; + } + oldType = newType; + } + [versionSegements addObject:[currentSegment copy]]; + return [versionSegements copy]; +} + + + +@end diff --git a/MacPass/MPSettingsHelper.h b/MacPass/MPSettingsHelper.h index fac2ea77..e517fb2e 100644 --- a/MacPass/MPSettingsHelper.h +++ b/MacPass/MPSettingsHelper.h @@ -80,6 +80,7 @@ APPKIT_EXTERN NSString *const kMPSettingsKeyUpdatePasswordOnTemplateEntries; /* Plugins */ APPKIT_EXTERN NSString *const kMPSettingsKeyLoadUnsecurePlugins; // If set to YES this will load all plugins regardless of their codesignature status APPKIT_EXTERN NSString *const kMPSettingsKeyDisabledPlugins; // NSArray of bundle identifiers of disabled plugins +APPKIT_EXTERN NSString *const kMPSettingsKeyLoadIncompatiblePlugins; // If set to YES incompatible plugins (no version info, marked as incompatible, etc) will be loaded regardless typedef NS_ENUM(NSUInteger, MPFileChangeStrategy) { MPFileChangeStrategyAsk, diff --git a/MacPass/MPSettingsHelper.m b/MacPass/MPSettingsHelper.m index 6cfef0b7..f1f08f62 100644 --- a/MacPass/MPSettingsHelper.m +++ b/MacPass/MPSettingsHelper.m @@ -75,6 +75,7 @@ NSString *const kMPSettingsKeyDoubleClickTitleAction = @"Double NSString *const kMPSettingsKeyUpdatePasswordOnTemplateEntries = @"UpdatePasswordOnTemplateEntries"; NSString *const kMPSettingsKeyLoadUnsecurePlugins = @"LoadUnsecurePlugins"; +NSString *const kMPSettingsKeyLoadIncompatiblePlugins = @"LoadIncompatiblePlugins"; NSString *const kMPSettingsKeyDisabledPlugins = @"DisabledPlugins"; /* Deprecated */ @@ -147,7 +148,8 @@ NSString *const kMPDepricatedSettingsKeyLoadUnsecurePlugins = @"MP kMPSettingsKeyDoubleClickTitleAction: @(MPDoubleClickTitleActionInspect), kMPSettingsKeyLoadUnsecurePlugins: @NO, kMPSettingsKeyUpdatePasswordOnTemplateEntries: @YES, - kMPSettingsKeyDisabledPlugins: @[] + kMPSettingsKeyDisabledPlugins: @[], + kMPSettingsKeyLoadIncompatiblePlugins: @NO }; }); return standardDefaults; diff --git a/MacPass/PluginRepositoryBrowserView.xib b/MacPass/PluginRepositoryBrowserView.xib index 382af136..8301fea3 100644 --- a/MacPass/PluginRepositoryBrowserView.xib +++ b/MacPass/PluginRepositoryBrowserView.xib @@ -161,7 +161,7 @@ - +