diff --git a/AppController.h b/AppController.h index 0eba7b55..24066459 100755 --- a/AppController.h +++ b/AppController.h @@ -29,7 +29,6 @@ #import #import #import -#import "SelfControlCommon.h" #import "SCSettings.h" // The main controller for the SelfControl app, which includes several methods @@ -56,14 +55,6 @@ @property (assign) BOOL addingBlock; -// Returns an autoreleased instance of the path to the helper tool inside -// SelfControl's bundle -@property (nonatomic, readonly, copy) NSString *selfControlHelperToolPath; - -// Returns as a UTF-8 encoded C-string the path to the helper tool inside -// SelfControl's bundle -- (char*)selfControlHelperToolPathUTF8String; - // Called when the block duration slider is moved. Updates the label that gives // the block duration in words (hours and minutes). - (IBAction)updateTimeSliderDisplay:(id)sender; @@ -82,16 +73,13 @@ // user interface. Called very often by several parts of the program. - (void)refreshUserInterface; -- (void)handleConfigurationChangedNotification; - // Called when the "Edit blocklist" button is clicked or the menu item is // selected. Allocates a new DomainListWindowController if necessary and opens // the domain blocklist window. Spawns an alert box if a block is in progress. - (IBAction)showDomainList:(id)sender; -// Returns YES if, according to a flag set in the user defaults system, the -// SelfControl launchd daemon (and therefore the block) is loaded. Returns NO -// if it is not. +// Returns YES if, according to settings or the hostfile, the +// SelfControl block is running Returns NO if it is not. @property (nonatomic, readonly) BOOL blockIsRunning; // Allocates a new TimerWindowController if necessary and opens the timer window. @@ -110,17 +98,13 @@ // Called by timerWindowController_ after its sheet returns, to add a specified // host to the blocklist (and refresh the block to use the new blocklist). Launches -// a new thread with refreshBlock: +// a new thread with addToBlocklist: - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock; // Called by timerWindowController_ after its sheet returns, to add a specified -// number of minutes to the black timer. Launches a new thread with refreshBlock. +// number of minutes to the black timer. - (void)extendBlockTime:(NSInteger)minutes lock:(NSLock*)lock; -// Converts a failure exit code from a helper tool invocation into an NSError, -// ready to be presented to the user. -- (NSError*)errorFromHelperToolStatusCode:(int)status; - // Gets authorization for and then immediately adds the block by calling // SelfControl's helper tool with the appropriate arguments. Meant to be called // as a separate thread. @@ -129,7 +113,7 @@ // Gets authorization for and then immediately refreshes the block by calling // SelfControl's helper tool with the appropriate arguments. Meant to be called // as a separate thread. -- (void)refreshBlock:(NSLock*)lockToUse; +- (void)updateActiveBlocklist:(NSLock*)lockToUse; // open preferences panel - (IBAction)openPreferences:(id)sender; @@ -148,6 +132,7 @@ // Changed property to manual accessor for pre-Leopard compatibility @property (nonatomic, readonly, strong) id initialWindow; +// opens the SelfControl FAQ in the default browser - (IBAction)openFAQ:(id)sender; @end diff --git a/AppController.m b/AppController.m index d7626831..f868808f 100755 --- a/AppController.m +++ b/AppController.m @@ -25,12 +25,19 @@ #import "PreferencesGeneralViewController.h" #import "PreferencesAdvancedViewController.h" #import "SCTimeIntervalFormatter.h" -#import "SCUtilities.h" #import #import #import "SCSettings.h" +#import +#import "SCXPCClient.h" +#import "HostFileBlocker.h" +#import "SCBlockFileReaderWriter.h" -NSString* const kSelfControlErrorDomain = @"SelfControlErrorDomain"; +@interface AppController () {} + +@property (atomic, strong, readwrite) SCXPCClient* xpc; + +@end @implementation AppController { NSWindowController* getStartedWindowController; @@ -42,20 +49,7 @@ - (AppController*) init { if(self = [super init]) { defaults_ = [NSUserDefaults standardUserDefaults]; - settings_ = [SCSettings currentUserSettings]; - - NSDictionary* appDefaults = @{ - @"HighlightInvalidHosts": @YES, - @"VerifyInternetConnection": @YES, - @"TimerWindowFloats": @NO, - @"BadgeApplicationIcon": @YES, - @"MaxBlockLength": @1440, - @"BlockLengthInterval": @15, - @"WhitelistAlertSuppress": @NO, - @"GetStartedShown": @NO - }; - - [defaults_ registerDefaults:appDefaults]; + [defaults_ registerDefaults: SCConstants.defaultUserDefaults]; self.addingBlock = false; @@ -68,32 +62,6 @@ - (AppController*) init { return self; } -- (NSString*)selfControlHelperToolPath { - static NSString* path; - - // Cache the path so it doesn't have to be searched for again. - if(!path) { - NSBundle* thisBundle = [NSBundle mainBundle]; - path = [thisBundle pathForAuxiliaryExecutable: @"org.eyebeam.SelfControl"]; - } - - return path; -} - -- (char*)selfControlHelperToolPathUTF8String { - static char* path; - - // Cache the converted path so it doesn't have to be converted again - if(!path) { - path = malloc(512); - [[self selfControlHelperToolPath] getCString: path - maxLength: 512 - encoding: NSUTF8StringEncoding]; - } - - return path; -} - - (IBAction)updateTimeSliderDisplay:(id)sender { NSInteger numMinutes = [defaults_ integerForKey: @"BlockDuration"]; @@ -110,18 +78,18 @@ - (IBAction)updateTimeSliderDisplay:(id)sender { NSString* timeString = [self timeSliderDisplayStringFromNumberOfMinutes:numMinutes]; [blockSliderTimeDisplayLabel_ setStringValue:timeString]; - [submitButton_ setEnabled: (numMinutes > 0) && ([[settings_ valueForKey: @"Blocklist"] count] > 0)]; + [submitButton_ setEnabled: (numMinutes > 0) && ([[defaults_ arrayForKey: @"Blocklist"] count] > 0)]; } - (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes { static NSCalendar* gregorian = nil; if (gregorian == nil) { - gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; + gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; } NSRange secondsRangePerMinute = [gregorian - rangeOfUnit:NSSecondCalendarUnit - inUnit:NSMinuteCalendarUnit + rangeOfUnit:NSCalendarUnitSecond + inUnit:NSCalendarUnitMinute forDate:[NSDate date]]; NSUInteger numberOfSecondsPerMinute = NSMaxRange(secondsRangePerMinute); @@ -142,23 +110,19 @@ - (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSe } - (IBAction)addBlock:(id)sender { - [defaults_ synchronize]; if ([self blockIsRunning]) { // This method shouldn't be getting called, a block is on so the Start button should be disabled. - NSError* err = [NSError errorWithDomain:kSelfControlErrorDomain - code: -102 - userInfo: @{NSLocalizedDescriptionKey: @"We can't start a block, because one is currently ongoing."}]; - [NSApp presentError: err]; + NSError* err = [SCErr errorWithCode: 104]; + [SCSentry captureError: err]; + [NSApp presentError: err]; return; } - if([[settings_ valueForKey:@"Blocklist"] count] == 0) { + if([[defaults_ arrayForKey: @"Blocklist"] count] == 0) { // Since the Start button should be disabled when the blocklist has no entries, // this should definitely not be happening. Exit. - NSError* err = [NSError errorWithDomain:kSelfControlErrorDomain - code: -102 - userInfo: @{NSLocalizedDescriptionKey: @"Error -102: Attempting to add block, but no blocklist is set."}]; - + NSError* err = [SCErr errorWithCode: 101]; + [SCSentry captureError: err]; [NSApp presentError: err]; return; @@ -199,7 +163,7 @@ - (void)refreshUserInterface { // already refreshing the UI, no need to wait and do it again return; } - + BOOL blockWasOn = blockIsOn; blockIsOn = [self blockIsRunning]; @@ -210,12 +174,8 @@ - (void)refreshUserInterface { [initialWindow_ close]; [self closeDomainList]; } - } else { // block is off + } else { // block is off if(blockWasOn) { // if we just switched states to off... - // Now that the current block is over, we can go ahead and remove the legacy block info - // and migrate them to the new SCSettings system - [[SCSettings currentUserSettings] clearLegacySettings]; - [timerWindowController_ blockEnded]; // Makes sure the domain list will refresh when it comes back @@ -234,20 +194,16 @@ - (void)refreshUserInterface { [self closeTimerWindow]; } - [defaults_ synchronize]; - [self updateTimeSliderDisplay: blockDurationSlider_]; - BOOL addBlockIsOngoing = self.addingBlock; - - if([defaults_ integerForKey: @"BlockDuration"] != 0 && [[settings_ valueForKey: @"Blocklist"] count] != 0 && !addBlockIsOngoing) { + if([defaults_ integerForKey: @"BlockDuration"] != 0 && [[defaults_ arrayForKey: @"Blocklist"] count] != 0 && !self.addingBlock) { [submitButton_ setEnabled: YES]; } else { [submitButton_ setEnabled: NO]; } // If we're adding a block, we want buttons disabled. - if(!addBlockIsOngoing) { + if(!self.addingBlock) { [blockDurationSlider_ setEnabled: YES]; [editBlocklistButton_ setEnabled: YES]; [submitButton_ setTitle: NSLocalizedString(@"Start", @"Start button")]; @@ -260,14 +216,13 @@ - (void)refreshUserInterface { // if block's off, and we haven't shown it yet, show the first-time modal if (![defaults_ boolForKey: @"GetStartedShown"]) { [defaults_ setBool: YES forKey: @"GetStartedShown"]; - [defaults_ synchronize]; [self showGetStartedWindow: self]; } } // finally: if the helper tool marked that it detected tampering, make sure // we follow through and set the cheater wallpaper (helper tool can't do it itself) - if ([[settings_ valueForKey: @"TamperingDetected"] boolValue]) { + if ([settings_ boolForKey: @"TamperingDetected"]) { NSURL* cheaterBackgroundURL = [[NSBundle mainBundle] URLForResource: @"cheater-background" withExtension: @"png"]; NSArray* screens = [NSScreen screens]; for (NSScreen* screen in screens) { @@ -281,7 +236,7 @@ - (void)refreshUserInterface { } // Display "blocklist" or "allowlist" as appropriate - NSString* listType = [[settings_ valueForKey: @"BlockAsWhitelist"] boolValue] ? @"Allowlist" : @"Blocklist"; + NSString* listType = [defaults_ boolForKey: @"BlockAsWhitelist"] ? @"Allowlist" : @"Blocklist"; NSString* editListString = NSLocalizedString(([NSString stringWithFormat: @"Edit %@", listType]), @"Edit list button / menu item"); editBlocklistButton_.title = editListString; @@ -291,15 +246,16 @@ - (void)refreshUserInterface { } - (void)handleConfigurationChangedNotification { + [SCSentry addBreadcrumb: @"Received configuration changed notification" category: @"app"]; // if our configuration changed, we should assume the settings may have changed - [[SCSettings currentUserSettings] reloadSettings]; + [[SCSettings sharedSettings] reloadSettings]; // and our interface may need to change to match! [self refreshUserInterface]; } - (void)showTimerWindow { if(timerWindowController_ == nil) { - [NSBundle loadNibNamed: @"TimerWindow" owner: self]; + [[NSBundle mainBundle] loadNibNamed: @"TimerWindow" owner: self topLevelObjects: nil]; } else { [[timerWindowController_ window] makeKeyAndOrderFront: self]; [[timerWindowController_ window] center]; @@ -312,6 +268,7 @@ - (void)closeTimerWindow { } - (IBAction)openPreferences:(id)sender { + [SCSentry addBreadcrumb: @"Opening preferences window" category: @"app"]; if (preferencesWindowController_ == nil) { NSViewController* generalViewController = [[PreferencesGeneralViewController alloc] init]; NSViewController* advancedViewController = [[PreferencesAdvancedViewController alloc] init]; @@ -323,6 +280,7 @@ - (IBAction)openPreferences:(id)sender { } - (IBAction)showGetStartedWindow:(id)sender { + [SCSentry addBreadcrumb: @"Showing \"Get Started\" window" category: @"app"]; if (!getStartedWindowController) { getStartedWindowController = [[NSWindowController alloc] initWithWindowNibName: @"FirstTime"]; } @@ -340,8 +298,51 @@ - (void)applicationWillFinishLaunching:(NSNotification *)notification { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [NSApplication sharedApplication].delegate = self; + + [SCSentry startSentry: @"org.eyebeam.SelfControl"]; + + settings_ = [SCSettings sharedSettings]; + // go copy over any preferences from legacy setting locations + // (we won't clear any old data yet - we leave that to the daemon) + if ([SCMigrationUtilities legacySettingsFoundForCurrentUser]) { + [SCMigrationUtilities copyLegacySettingsToDefaults]; + } + + // start up our daemon XPC + self.xpc = [SCXPCClient new]; + [self.xpc connectToHelperTool]; + + // if we don't have a connection within 0.5 seconds, + // OR we get back a connection with an old daemon version + // AND we're running a modern block (which should have a daemon running it) + // something's wrong with our app-daemon connection. This probably means one of two things: + // 1. The daemon got unloaded somehow and failed to restart. This is a big problem because the block won't come off. + // 2. The daemon doesn't want to talk to us anymore, potentially because we've changed our signing certificate. This is a + // smaller problem, but still not great because the app can't communicate anything to the daemon. + // 3. There's a daemon but it's an old version, and should be replaced. + // in any case, let's go try to reinstall the daemon + // (we debounce this call so it happens only once, after the connection has been invalidated for an extended period) + if ([SCBlockUtilities modernBlockIsRunning]) { + [NSTimer scheduledTimerWithTimeInterval: 0.5 repeats: NO block:^(NSTimer * _Nonnull timer) { + [self.xpc getVersion:^(NSString * _Nonnull daemonVersion, NSError * _Nonnull error) { + if (error == nil) { + if ([SELFCONTROL_VERSION_STRING compare: daemonVersion options: NSNumericSearch] == NSOrderedDescending) { + NSLog(@"Daemon version of %@ is out of date (current version is %@).", daemonVersion, SELFCONTROL_VERSION_STRING); + [SCSentry addBreadcrumb: @"Detected out-of-date daemon" category: @"app"]; + [self reinstallDaemon]; + } else { + [SCSentry addBreadcrumb: @"Detected up-to-date daemon" category:@"app"]; + NSLog(@"Daemon version of %@ is up-to-date!", daemonVersion); + } + } else { + NSLog(@"ERROR: Fetching daemon version failed with error %@", error); + [self reinstallDaemon]; + } + }]; + }]; + } - // Register observers on both distributed and normal notification centers + // Register observers on both distributed and normal notification centers // to receive notifications from the helper tool and the other parts of the // main SelfControl app. Note that they are divided thusly because distributed // notifications are very expensive and should be minimized. @@ -375,10 +376,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [self refreshUserInterface]; - NSOperatingSystemVersion minRequiredVersion = (NSOperatingSystemVersion){10,8,0}; // Mountain Lion - NSString* minRequiredVersionString = @"10.8 (Mountain Lion)"; + NSOperatingSystemVersion minRequiredVersion = (NSOperatingSystemVersion){10,10,0}; // Mountain Lion + NSString* minRequiredVersionString = @"10.10 (Yosemite)"; if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: minRequiredVersion]) { NSLog(@"ERROR: Unsupported version for SelfControl"); + [SCSentry captureMessage: @"Unsupported operating system version"]; NSAlert* unsupportedVersionAlert = [[NSAlert alloc] init]; [unsupportedVersionAlert setMessageText: NSLocalizedString(@"Unsupported version", nil)]; [unsupportedVersionAlert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"This version of SelfControl only supports Mac OS X version %@ or higher. To download a version for older operating systems, please go to www.selfcontrolapp.com", nil), minRequiredVersionString]]; @@ -391,13 +393,36 @@ - (void)applicationWillTerminate:(NSNotification *)notification { [settings_ synchronizeSettings]; } +- (void)reinstallDaemon { + NSLog(@"Attempting to reinstall daemon..."); + [SCSentry addBreadcrumb: @"Reinstalling daemon" category:@"app"]; + [self.xpc installDaemon:^(NSError * _Nonnull error) { + if (error == nil) { + NSLog(@"Reinstalled daemon successfully!"); + [SCSentry addBreadcrumb: @"Daemon reinstalled successfully" category:@"app"]; + + NSLog(@"Retrying helper tool connection..."); + [self.xpc performSelectorOnMainThread: @selector(connectToHelperTool) withObject: nil waitUntilDone: YES]; + } else { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"ERROR: Reinstalling daemon failed with error %@", error); + [NSApp presentError: error]; + } + } + }]; +} + - (BOOL)blockIsRunning { - return [SCUtilities blockIsRunningWithSettings: settings_ defaults: defaults_]; + // we'll say a block is running if we find the block info, but + // also, importantly, if we find a block still going in the hosts file + // that way if this happens, the user will still see the timer window - + // which will let them manually clear the remaining block info after 10 seconds + return [SCBlockUtilities anyBlockIsRunning] || [HostFileBlocker blockFoundInHostsFile]; } - (IBAction)showDomainList:(id)sender { - BOOL addBlockIsOngoing = self.addingBlock; - if([self blockIsRunning] || addBlockIsOngoing) { + [SCSentry addBreadcrumb: @"Showing domain list" category:@"app"]; + if([self blockIsRunning] || self.addingBlock) { NSAlert* blockInProgressAlert = [[NSAlert alloc] init]; [blockInProgressAlert setMessageText: NSLocalizedString(@"Block in progress", @"Block in progress error title")]; [blockInProgressAlert setInformativeText:NSLocalizedString(@"The blocklist cannot be edited while a block is in progress.", @"Block in progress explanation")]; @@ -408,7 +433,7 @@ - (IBAction)showDomainList:(id)sender { } if(domainListWindowController_ == nil) { - [NSBundle loadNibNamed: @"DomainList" owner: self]; + [[NSBundle mainBundle] loadNibNamed: @"DomainList" owner: self topLevelObjects: nil]; } [domainListWindowController_ showWindow: self]; } @@ -442,8 +467,11 @@ - (BOOL)networkConnectionIsAvailable { } - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock { - NSMutableArray* list = [[settings_ valueForKey: @"Blocklist"] mutableCopy]; - NSArray* cleanedEntries = [SCUtilities cleanBlocklistEntry: host]; + NSLog(@"addToBlocklist: %@", host); + // Note we RETRIEVE the latest list from settings (ActiveBlocklist), but we SET the new list in defaults + // since the helper daemon should be the only one changing ActiveBlocklist + NSMutableArray* list = [[settings_ valueForKey: @"ActiveBlocklist"] mutableCopy]; + NSArray* cleanedEntries = [SCMiscUtilities cleanBlocklistEntry: host]; if (cleanedEntries.count == 0) return; @@ -452,7 +480,7 @@ - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock { [list addObject: entry]; } - [settings_ setValue: list forKey: @"Blocklist"]; + [defaults_ setValue: list forKey: @"Blocklist"]; if(![self blockIsRunning]) { // This method shouldn't be getting called, a block is not on. @@ -461,15 +489,8 @@ - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock { // before we return. [self refreshUserInterface]; - // Reverse the blocklist change made before we fail - NSMutableArray* list = [[settings_ valueForKey: @"Blocklist"] mutableCopy]; - [list removeLastObject]; - [settings_ setValue: list forKey: @"Blocklist"]; - - NSError* err = [NSError errorWithDomain:kSelfControlErrorDomain - code: -103 - userInfo: @{NSLocalizedDescriptionKey: @"Error -103: Attempting to add host to block, but no block appears to be in progress."}]; - + NSError* err = [SCErr errorWithCode: 102]; + [SCSentry captureError: err]; [NSApp presentError: err]; return; @@ -483,11 +504,6 @@ - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock { [networkUnavailableAlert addButtonWithTitle: NSLocalizedString(@"Network Diagnostics...", @"Network Diagnostics button")]; if([networkUnavailableAlert runModal] == NSAlertFirstButtonReturn) { // User clicked cancel - // Reverse the blocklist change made before we fail - NSMutableArray* list = [[settings_ valueForKey: @"Blocklist"] mutableCopy]; - [list removeLastObject]; - [settings_ setValue: list forKey: @"Blocklist"]; - return; } @@ -497,22 +513,19 @@ - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock { CFNetDiagnosticRef diagRef = CFNetDiagnosticCreateWithURL(kCFAllocatorDefault, url); CFNetDiagnosticDiagnoseProblemInteractively(diagRef); - // Reverse the blocklist change made before we fail - NSMutableArray* list = [[settings_ valueForKey: @"Blocklist"] mutableCopy]; - [list removeLastObject]; - [settings_ setValue: list forKey: @"Blocklist"]; - return; } - [NSThread detachNewThreadSelector: @selector(refreshBlock:) toTarget: self withObject: lock]; + [NSThread detachNewThreadSelector: @selector(updateActiveBlocklist:) toTarget: self withObject: lock]; } - (void)extendBlockTime:(NSInteger)minutesToAdd lock:(NSLock*)lock { // sanity check: extending a block for 0 minutes is useless; 24 hour should be impossible - NSInteger MINUTES_IN_DAY = 24 * 60 * 60; - if(minutesToAdd < 1 || minutesToAdd > MINUTES_IN_DAY) - return; + NSInteger maxBlockLength = [defaults_ integerForKey: @"MaxBlockLength"]; + if(minutesToAdd < 1) return; + if (minutesToAdd > maxBlockLength) { + minutesToAdd = maxBlockLength; + } // ensure block health before we try to change it if(![self blockIsRunning]) { @@ -522,21 +535,20 @@ - (void)extendBlockTime:(NSInteger)minutesToAdd lock:(NSLock*)lock { // before we return. [self refreshUserInterface]; - NSError* err = [NSError errorWithDomain:kSelfControlErrorDomain - code: -103 - userInfo: @{NSLocalizedDescriptionKey: @"Error -103: Attempting to extend block time, but no block appears to be in progress."}]; - + NSError* err = [SCErr errorWithCode: 103]; + [SCSentry captureError: err]; [NSApp presentError: err]; return; } - - [NSThread detachNewThreadSelector: @selector(extendBlockDuration:) - toTarget: self - withObject: @{ - @"lock": lock, - @"minutesToAdd": @(minutesToAdd) - }]; + + [self updateBlockEndDate: lock minutesToAdd: minutesToAdd]; +// [NSThread detachNewThreadSelector: @selector(extendBlockDuration:) +// toTarget: self +// withObject: @{ +// @"lock": lock, +// @"minutesToAdd": @(minutesToAdd) +// }]; } - (void)dealloc { @@ -560,292 +572,102 @@ - (void)setDomainListWindowController:(id)newController { domainListWindowController_ = newController; } -- (NSError*)errorFromHelperToolStatusCode:(int)status { - NSString* domain = kSelfControlErrorDomain; - NSMutableString* description = [NSMutableString stringWithFormat: @"Error %d: ", status]; - switch(status) { - case -201: - [description appendString: @"Helper tool not launched as root."]; - break; - case -202: - [description appendString: @"Helper tool launched with insufficient arguments."]; - break; - case -203: - [description appendString: @"Host blocklist not set"]; - break; - case -204: - [description appendString: @"Could not write launchd plist file to LaunchDaemons folder."]; - break; - case -205: - [description appendString: @"Could not create PrivilegedHelperTools directory."]; - break; - case -206: - [description appendString: @"Could not change permissions on PrivilegedHelperTools directory."]; - break; - case -207: - [description appendString: @"Could not delete old helper binary."]; - break; - case -208: - [description appendString: @"Could not copy SelfControl's helper binary to PrivilegedHelperTools directory."]; - break; - case -209: - [description appendString: @"Could not change permissions on SelfControl's helper binary."]; - break; - case -210: - [description appendString: @"Insufficient block information found."]; - break; - case -211: - [description appendString: @"Launch daemon load returned a failure status code."]; - break; - case -212: - [description appendString: @"Remove option called."]; - break; - case -213: - [description appendString: @"Refreshing domain blocklist, but no block is currently ongoing."]; - break; - case -214: - [description appendString: @"Insufficient block information found."]; - break; - case -215: - [description appendString: @"Checkup ran but no block found."]; - break; - case -216: - [description appendString: @"Could not write lock file."]; - break; - case -217: - [description appendString: @"Could not write lock file."]; - break; - case -218: - [description appendString: @"Could not remove SelfControl lock file."]; - break; - case -219: - [description appendString: @"SelfControl lock file already exists. Please try your block again."]; - break; - - default: - [description appendString: [NSString stringWithFormat: @"Helper tool failed with unknown error code: %d", status]]; - } - - return [NSError errorWithDomain: domain code: status userInfo: @{NSLocalizedDescriptionKey: description}]; -} - - (void)installBlock { + [SCSentry addBreadcrumb: @"App running installBlock method" category:@"app"]; @autoreleasepool { self.addingBlock = true; [self refreshUserInterface]; - AuthorizationRef authorizationRef; - char* helperToolPath = [self selfControlHelperToolPathUTF8String]; - NSUInteger helperToolPathSize = strlen(helperToolPath); - AuthorizationItem right = { - kAuthorizationRightExecute, - helperToolPathSize, - helperToolPath, - 0 - }; - AuthorizationRights authRights = { - 1, - &right - }; - AuthorizationFlags myFlags = kAuthorizationFlagDefaults | - kAuthorizationFlagExtendRights | - kAuthorizationFlagInteractionAllowed; - OSStatus status; - - status = AuthorizationCreate (&authRights, - kAuthorizationEmptyEnvironment, - myFlags, - &authorizationRef); - - if(status) { - NSLog(@"ERROR: Failed to authorize block start."); - self.addingBlock = false; - [self refreshUserInterface]; - return; - } - - // for legacy reasons, BlockDuration is in minutes, so convert it to seconds before passing it through] - NSTimeInterval blockDurationSecs = [[defaults_ valueForKey: @"BlockDuration"] intValue] * 60; - [SCUtilities startBlockInSettings: settings_ withBlockDuration: blockDurationSecs]; - - // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid - [settings_ synchronizeSettings]; - - // We need to pass our UID to the helper tool. It needs to know whose defaults - // it should reading in order to properly load the blocklist. - char uidString[32]; - snprintf(uidString, sizeof(uidString), "%d", getuid()); - - FILE* commPipe; - - char* args[] = { uidString, "--install", NULL }; - status = AuthorizationExecuteWithPrivileges(authorizationRef, - helperToolPath, - kAuthorizationFlagDefaults, - args, - &commPipe); - - if(status) { - NSLog(@"WARNING: Authorized execution of helper tool returned failure status code %d", (int)status); - - // reset settings on failure, and record that on disk ASAP - [SCUtilities removeBlockFromSettings: settings_]; - [settings_ synchronizeSettings]; - - NSError* err = [NSError errorWithDomain: kSelfControlErrorDomain - code: status - userInfo: @{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"Error %d received from the Security Server.", (int)status]}]; - - [NSApp performSelectorOnMainThread: @selector(presentError:) - withObject: err - waitUntilDone: YES]; - - self.addingBlock = false; - [self refreshUserInterface]; - - return; - } - - NSFileHandle* helperToolHandle = [[NSFileHandle alloc] initWithFileDescriptor: fileno(commPipe) closeOnDealloc: YES]; - - NSData* inData = [helperToolHandle readDataToEndOfFile]; - - - NSString* inDataString = [[NSString alloc] initWithData: inData encoding: NSUTF8StringEncoding]; - - if([inDataString isEqualToString: @""]) { - // reset settings on failure, and record that on disk ASAP - [SCUtilities removeBlockFromSettings: settings_]; - [settings_ synchronizeSettings]; - - NSError* err = [NSError errorWithDomain: kSelfControlErrorDomain - code: -104 - userInfo: @{NSLocalizedDescriptionKey: @"Error -104: The helper tool crashed. This may cause unexpected errors."}]; - - [NSApp performSelectorOnMainThread: @selector(presentError:) - withObject: err - waitUntilDone: YES]; - } - - int exitCode = [inDataString intValue]; - - if(exitCode) { - // reset settings on failure, and record that on disk ASAP - [SCUtilities removeBlockFromSettings: settings_]; - [settings_ synchronizeSettings]; - - NSError* err = [self errorFromHelperToolStatusCode: exitCode]; - - [NSApp performSelectorOnMainThread: @selector(presentError:) - withObject: err - waitUntilDone: YES]; - } - - self.addingBlock = false; - [self refreshUserInterface]; + [self.xpc installDaemon:^(NSError * _Nonnull error) { + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + [NSApp performSelectorOnMainThread: @selector(presentError:) + withObject: error + waitUntilDone: YES]; + } + self.addingBlock = false; + [self refreshUserInterface]; + return; + } else { + [SCSentry addBreadcrumb: @"Daemon installed successfully (en route to installing block)" category:@"app"]; + // helper tool installed successfully, let's prepare to start the block! + // for legacy reasons, BlockDuration is in minutes, so convert it to seconds before passing it through] + // sanity check duration (must be above zero) + NSTimeInterval blockDurationSecs = MAX([[self->defaults_ valueForKey: @"BlockDuration"] intValue] * 60, 0); + NSDate* newBlockEndDate = [NSDate dateWithTimeIntervalSinceNow: blockDurationSecs]; + + // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid + [self->settings_ synchronizeSettings]; + [self->defaults_ synchronize]; + + // ok, the new helper tool is installed! refresh the connection, then it's time to start the block + [self.xpc refreshConnectionAndRun:^{ + NSLog(@"Refreshed connection and ready to start block!"); + [self.xpc startBlockWithControllingUID: getuid() + blocklist: [self->defaults_ arrayForKey: @"Blocklist"] + isAllowlist: [self->defaults_ boolForKey: @"BlockAsWhitelist"] + endDate: newBlockEndDate + blockSettings: @{ + @"ClearCaches": [self->defaults_ valueForKey: @"ClearCaches"], + @"AllowLocalNetworks": [self->defaults_ valueForKey: @"AllowLocalNetworks"], + @"EvaluateCommonSubdomains": [self->defaults_ valueForKey: @"EvaluateCommonSubdomains"], + @"IncludeLinkedDomains": [self->defaults_ valueForKey: @"IncludeLinkedDomains"], + @"BlockSoundShouldPlay": [self->defaults_ valueForKey: @"BlockSoundShouldPlay"], + @"BlockSound": [self->defaults_ valueForKey: @"BlockSound"], + @"EnableErrorReporting": [self->defaults_ valueForKey: @"EnableErrorReporting"] + } + reply:^(NSError * _Nonnull error) { + if (error != nil ) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + [NSApp performSelectorOnMainThread: @selector(presentError:) + withObject: error + waitUntilDone: YES]; + } + } else { + [SCSentry addBreadcrumb: @"Block started successfully" category:@"app"]; + } + + // get the new settings + [self->settings_ synchronizeSettingsWithCompletion:^(NSError * _Nullable error) { + self.addingBlock = false; + [self refreshUserInterface]; + }]; + }]; + }]; + } + }]; } } -- (void)refreshBlock:(NSLock*)lockToUse { +- (void)updateActiveBlocklist:(NSLock*)lockToUse { if(![lockToUse tryLock]) { return; } + + [SCSentry addBreadcrumb: @"App running updateActiveBlocklist method" category:@"app"]; - @autoreleasepool { - AuthorizationRef authorizationRef; - char* helperToolPath = [self selfControlHelperToolPathUTF8String]; - long helperToolPathSize = strlen(helperToolPath); - AuthorizationItem right = { - kAuthorizationRightExecute, - helperToolPathSize, - helperToolPath, - 0 - }; - AuthorizationRights authRights = { - 1, - &right - }; - AuthorizationFlags myFlags = kAuthorizationFlagDefaults | - kAuthorizationFlagExtendRights | - kAuthorizationFlagInteractionAllowed; - OSStatus status; - - status = AuthorizationCreate (&authRights, - kAuthorizationEmptyEnvironment, - myFlags, - &authorizationRef); - - if(status) { - NSLog(@"ERROR: Failed to authorize block refresh."); - - // Reverse the blocklist change made before we fail - NSMutableArray* list = [[settings_ valueForKey: @"Blocklist"] mutableCopy]; - [list removeLastObject]; - [settings_ setValue: list forKey: @"Blocklist"]; - - [lockToUse unlock]; - - return; - } - - // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid - [settings_ synchronizeSettings]; - - // We need to pass our UID to the helper tool. It needs to know whose defaults - // it should read in order to properly load the blocklist. - char uidString[32]; - snprintf(uidString, sizeof(uidString), "%d", getuid()); - - FILE* commPipe; - - char* args[] = { uidString, "--refresh", NULL }; - status = AuthorizationExecuteWithPrivileges(authorizationRef, - helperToolPath, - kAuthorizationFlagDefaults, - args, - &commPipe); - - if(status) { - NSLog(@"WARNING: Authorized execution of helper tool returned failure status code %d", (int)status); - - NSError* err = [self errorFromHelperToolStatusCode: status]; - - [NSApp performSelectorOnMainThread: @selector(presentError:) - withObject: err - waitUntilDone: YES]; - - [lockToUse unlock]; - - return; - } - - NSFileHandle* helperToolHandle = [[NSFileHandle alloc] initWithFileDescriptor: fileno(commPipe) closeOnDealloc: YES]; - - NSData* inData = [helperToolHandle readDataToEndOfFile]; - NSString* inDataString = [[NSString alloc] initWithData: inData encoding: NSUTF8StringEncoding]; - - if([inDataString isEqualToString: @""]) { - NSError* err = [NSError errorWithDomain: kSelfControlErrorDomain - code: -105 - userInfo: @{NSLocalizedDescriptionKey: @"Error -105: The helper tool crashed. This may cause unexpected errors."}]; - - [NSApp performSelectorOnMainThread: @selector(presentError:) - withObject: err - waitUntilDone: YES]; - } - - int exitCode = [inDataString intValue]; - - if(exitCode) { - NSError* err = [self errorFromHelperToolStatusCode: exitCode]; - - [NSApp performSelectorOnMainThread: @selector(presentError:) - withObject: err - waitUntilDone: YES]; - } + // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid + [settings_ synchronizeSettings]; + [defaults_ synchronize]; - [timerWindowController_ performSelectorOnMainThread:@selector(closeAddSheet:) withObject: self waitUntilDone: YES]; - } - [lockToUse unlock]; + [self.xpc refreshConnectionAndRun:^{ + NSLog(@"Refreshed connection updating active blocklist!"); + [self.xpc updateBlocklist: [self->defaults_ arrayForKey: @"Blocklist"] + reply:^(NSError * _Nonnull error) { + [self->timerWindowController_ performSelectorOnMainThread:@selector(closeAddSheet:) withObject: self waitUntilDone: YES]; + + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + [NSApp performSelectorOnMainThread: @selector(presentError:) + withObject: error + waitUntilDone: YES]; + } + } else { + [SCSentry addBreadcrumb: @"Blocklist updated successfully" category:@"app"]; + } + + [lockToUse unlock]; + }]; + }]; } // it really sucks, but we can't change any values that are KVO-bound to the UI unless they're on the main thread @@ -856,32 +678,52 @@ - (void)setDefaultsBlockDurationOnMainThread:(NSNumber*)newBlockDuration { } [defaults_ setInteger: [newBlockDuration intValue] forKey: @"BlockDuration"]; - [defaults_ synchronize]; } -- (void)extendBlockDuration:(NSDictionary*)options { - NSInteger minutesToAdd = [options[@"minutesToAdd"] integerValue]; +- (void)updateBlockEndDate:(NSLock*)lockToUse minutesToAdd:(NSInteger)minutesToAdd { + if(![lockToUse tryLock]) { + return; + } + [SCSentry addBreadcrumb: @"App running updateBlockEndDate method" category:@"app"]; + minutesToAdd = MAX(minutesToAdd, 0); // make sure there's no funny business with negative minutes - NSDate* oldBlockEndDate = [settings_ valueForKey: @"BlockEndDate"]; NSDate* newBlockEndDate = [oldBlockEndDate dateByAddingTimeInterval: (minutesToAdd * 60)]; - - // Before we try to extend the block, make sure the block time didn't run out (or is about to run out) in the meantime - if (![SCUtilities blockShouldBeRunningInDictionary: settings_.dictionaryRepresentation] || [oldBlockEndDate timeIntervalSinceNow] < 1) { - // we're done, or will be by the time we get to it! so just let it expire. they can restart it. - return; - } - // set the new block end date - [settings_ setValue: newBlockEndDate forKey: @"BlockEndDate"]; - - // synchronize it to disk to the helper tool knows immediately + // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid [settings_ synchronizeSettings]; - - // let the timer know it needs to recalculate - [timerWindowController_ performSelectorOnMainThread:@selector(blockEndDateUpdated) - withObject: nil - waitUntilDone: YES]; + [defaults_ synchronize]; + + [self.xpc refreshConnectionAndRun:^{ + // Before we try to extend the block, make sure the block time didn't run out (or is about to run out) in the meantime + if ([SCBlockUtilities currentBlockIsExpired] || [oldBlockEndDate timeIntervalSinceNow] < 1) { + // we're done, or will be by the time we get to it! so just let it expire. they can restart it. + [lockToUse unlock]; + return; + } + + NSLog(@"Refreshed connection updating active block end date!"); + [self.xpc updateBlockEndDate: newBlockEndDate + reply:^(NSError * _Nonnull error) { + [self->timerWindowController_ performSelectorOnMainThread:@selector(closeAddSheet:) withObject: self waitUntilDone: YES]; + // let the timer know it needs to recalculate + [self->timerWindowController_ performSelectorOnMainThread:@selector(blockEndDateUpdated) + withObject: nil + waitUntilDone: YES]; + + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + [NSApp performSelectorOnMainThread: @selector(presentError:) + withObject: error + waitUntilDone: YES]; + } + } else { + [SCSentry addBreadcrumb: @"App extended block duration successfully" category:@"app"]; + } + + [lockToUse unlock]; + }]; + }]; } - (IBAction)save:(id)sender { @@ -896,66 +738,72 @@ - (IBAction)save:(id)sender { runResult = [sp runModal]; /* if successful, save file under designated name */ - if (runResult == NSOKButton) { - NSString* errDescription; - [SCUtilities writeBlocklistToFileURL: sp.URL settings: settings_ errorDescription: &errDescription]; - - if(errDescription) { - NSError* displayErr = [NSError errorWithDomain: kSelfControlErrorDomain code: -902 userInfo: @{NSLocalizedDescriptionKey: [@"Error 902: " stringByAppendingString: errDescription]}]; + if (runResult == NSModalResponseOK) { + NSError* err; + [SCBlockFileReaderWriter writeBlocklistToFileURL: sp.URL + blockInfo: @{ + @"Blocklist": [defaults_ arrayForKey: @"Blocklist"], + @"BlockAsWhitelist": [defaults_ objectForKey: @"BlockAsWhitelist"] + + } + error: &err]; + + if (err != nil) { + NSError* displayErr = [SCErr errorWithCode: 105 subDescription: err.localizedDescription]; + [SCSentry captureError: displayErr]; NSBeep(); [NSApp presentError: displayErr]; return; - } + } else { + [SCSentry addBreadcrumb: @"Saved blocklist to file" category:@"app"]; + } } } +- (BOOL)openSavedBlockFileAtURL:(NSURL*)fileURL { + NSDictionary* settingsFromFile = [SCBlockFileReaderWriter readBlocklistFromFile: fileURL]; + + if (settingsFromFile != nil) { + [defaults_ setObject: settingsFromFile[@"Blocklist"] forKey: @"Blocklist"]; + [defaults_ setObject: settingsFromFile[@"BlockAsWhitelist"] forKey: @"BlockAsWhitelist"]; + [SCSentry addBreadcrumb: @"Opened blocklist from file" category:@"app"]; + } else { + NSLog(@"WARNING: Could not read a valid blocklist from file - ignoring."); + return NO; + } + + // close the domain list (and reopen again if need be to refresh) + BOOL domainListIsOpen = [[domainListWindowController_ window] isVisible]; + NSRect frame = [[domainListWindowController_ window] frame]; + [self closeDomainList]; + if(domainListIsOpen) { + [self showDomainList: self]; + [[domainListWindowController_ window] setFrame: frame display: YES]; + } + + [self refreshUserInterface]; + return YES; +} + - (IBAction)open:(id)sender { NSOpenPanel* oPanel = [NSOpenPanel openPanel]; oPanel.allowedFileTypes = @[@"selfcontrol"]; oPanel.allowsMultipleSelection = NO; long result = [oPanel runModal]; - if (result == NSOKButton) { + if (result == NSModalResponseOK) { if([oPanel.URLs count] > 0) { - [SCUtilities readBlocklistFromFile: oPanel.URLs[0] toSettings: settings_]; - - // close the domain list (and reopen again if need be to refresh) - BOOL domainListIsOpen = [[domainListWindowController_ window] isVisible]; - NSRect frame = [[domainListWindowController_ window] frame]; - [self closeDomainList]; - if(domainListIsOpen) { - [self showDomainList: self]; - [[domainListWindowController_ window] setFrame: frame display: YES]; - } - - [self refreshUserInterface]; + [self openSavedBlockFileAtURL: oPanel.URLs[0]]; } } } - (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename { - NSDictionary* openedDict = [NSDictionary dictionaryWithContentsOfFile: filename]; - if(openedDict == nil) return NO; - - NSArray* newBlocklist = openedDict[@"HostBlacklist"]; - NSNumber* newAllowlistChoice = openedDict[@"BlockAsWhitelist"]; - if(newBlocklist == nil || newAllowlistChoice == nil) return NO; - - [settings_ setValue: newBlocklist forKey: @"Blocklist"]; - [settings_ setValue: newAllowlistChoice forKey: @"BlockAsWhitelist"]; - - BOOL domainListIsOpen = [[domainListWindowController_ window] isVisible]; - NSRect frame = [[domainListWindowController_ window] frame]; - [self closeDomainList]; - if(domainListIsOpen) { - [self showDomainList: self]; - [[domainListWindowController_ window] setFrame: frame display: YES]; - } - - return YES; + return [self openSavedBlockFileAtURL: [NSURL fileURLWithPath: filename]]; } - (IBAction)openFAQ:(id)sender { + [SCSentry addBreadcrumb: @"Opened SelfControl FAQ" category:@"app"]; NSURL *url=[NSURL URLWithString: @"https://github.com/SelfControlApp/selfcontrol/wiki/FAQ#q-selfcontrols-timer-is-at-0000-and-i-cant-start-a-new-block-and-im-freaking-out"]; [[NSWorkspace sharedWorkspace] openURL: url]; } diff --git a/Base.lproj/PreferencesAdvancedViewController.xib b/Base.lproj/PreferencesAdvancedViewController.xib index c0f4e3fb..e9776742 100644 --- a/Base.lproj/PreferencesAdvancedViewController.xib +++ b/Base.lproj/PreferencesAdvancedViewController.xib @@ -1,17 +1,13 @@ - + - + - - - - @@ -29,7 +25,7 @@ - + diff --git a/Base.lproj/PreferencesGeneralViewController.xib b/Base.lproj/PreferencesGeneralViewController.xib index 189222f2..61b997d6 100644 --- a/Base.lproj/PreferencesGeneralViewController.xib +++ b/Base.lproj/PreferencesGeneralViewController.xib @@ -1,14 +1,13 @@ - + - + - @@ -17,11 +16,11 @@ - + - + @@ -74,10 +73,23 @@ + + + - + diff --git a/AllowlistScraper.h b/Block Management/AllowlistScraper.h similarity index 67% rename from AllowlistScraper.h rename to Block Management/AllowlistScraper.h index 146d13b0..179dda9e 100644 --- a/AllowlistScraper.h +++ b/Block Management/AllowlistScraper.h @@ -8,8 +8,10 @@ #import +@class SCBlockEntry; + @interface AllowlistScraper : NSObject -+ (NSSet*)relatedDomains:(NSString*)domain; ++ (NSSet*)relatedBlockEntries:(NSString*)domain; @end diff --git a/AllowlistScraper.m b/Block Management/AllowlistScraper.m similarity index 78% rename from AllowlistScraper.m rename to Block Management/AllowlistScraper.m index 978f19d1..ebc408f3 100644 --- a/AllowlistScraper.m +++ b/Block Management/AllowlistScraper.m @@ -7,13 +7,14 @@ // #import "AllowlistScraper.h" +#import "SCBlockEntry.h" @implementation AllowlistScraper -+ (NSSet*)relatedDomains:(NSString*)domain; { ++ (NSSet*)relatedBlockEntries:(NSString*)domain; { NSURL* rootURL = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", domain]]; if (!rootURL) { - return nil;; + return nil; } // stale data is OK @@ -30,7 +31,7 @@ + (NSSet*)relatedDomains:(NSString*)domain; { } NSDataDetector* dataDetector = [[NSDataDetector alloc] initWithTypes: NSTextCheckingTypeLink error: nil]; - NSCountedSet* relatedDomains = [NSCountedSet set]; + NSCountedSet* relatedEntries = [NSCountedSet set]; [dataDetector enumerateMatchesInString: html options: kNilOptions range: NSMakeRange(0, [html length]) @@ -38,11 +39,11 @@ + (NSSet*)relatedDomains:(NSString*)domain; { if (([result.URL.scheme isEqualToString: @"http"] || [result.URL.scheme isEqualToString: @"https"]) && [result.URL.host length] && ![result.URL.host isEqualToString: rootURL.host]) { - [relatedDomains addObject: result.URL.host]; - } + [relatedEntries addObject: [SCBlockEntry entryWithHostname: result.URL.host]]; + } }]; - return relatedDomains; + return relatedEntries; } @end diff --git a/BlockManager.h b/Block Management/BlockManager.h similarity index 90% rename from BlockManager.h rename to Block Management/BlockManager.h index 00f96be7..5e2860f6 100644 --- a/BlockManager.h +++ b/Block Management/BlockManager.h @@ -25,6 +25,8 @@ #import "HostFileBlocker.h" #import "NSString+IPAddress.h" +@class SCBlockEntry; + @interface BlockManager : NSObject { NSOperationQueue* opQueue; PacketFilter* pf; @@ -41,11 +43,13 @@ - (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeCommonSubdomains:(BOOL)blockCommon; - (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeCommonSubdomains:(BOOL)blockCommon includeLinkedDomains:(BOOL)includeLinked; +- (void)enterAppendMode; +- (void)finishAppending; - (void)prepareToAddBlock; - (void)finalizeBlock; - (void)addBlockEntryFromString:(NSString*)entry; -- (void)addBlockEntryWithHostName:(NSString*)hostName port:(int)portNum maskLen:(int)maskLen; -- (void)addBlockEntries:(NSArray*)blockList; +- (void)addBlockEntry:(SCBlockEntry*)entry; +- (void)addBlockEntriesFromStrings:(NSArray*)blockList; - (BOOL)clearBlock; - (BOOL)forceClearBlock; - (BOOL)blockIsActive; @@ -53,6 +57,5 @@ - (NSArray*)commonSubdomainsForHostName:(NSString*)hostName; - (NSArray*)ipAddressesForDomainName:(NSString*)domainName; - (BOOL)domainIsGoogle:(NSString*)domainName; -- (NSDictionary*)parseHostString:(NSString*)hostString; @end diff --git a/BlockManager.m b/Block Management/BlockManager.m similarity index 70% rename from BlockManager.m rename to Block Management/BlockManager.m index 4bf7ea80..f4d15ff1 100644 --- a/BlockManager.m +++ b/Block Management/BlockManager.m @@ -22,9 +22,12 @@ #import "BlockManager.h" #import "AllowlistScraper.h" +#import "SCBlockEntry.h" @implementation BlockManager +BOOL appendMode = NO; + - (BlockManager*)init { return [self initAsAllowlist: NO allowLocal: YES includeCommonSubdomains: YES]; } @@ -72,8 +75,35 @@ - (void)prepareToAddBlock { } } +- (void)enterAppendMode { + if (isAllowlist) { + NSLog(@"ERROR: can't append to allowlist block"); + return; + } + if(![hostsBlocker containsSelfControlBlock]) { + NSLog(@"ERROR: can't append to hosts block that doesn't yet exist"); + return; + } + + hostsBlockingEnabled = YES; + appendMode = YES; + [pf enterAppendMode]; +} +- (void)finishAppending { + NSLog(@"About to run operation queue for appending..."); + [opQueue waitUntilAllOperationsAreFinished]; + NSLog(@"Operation queue ran!"); + + [hostsBlocker writeNewFileContents]; + [pf finishAppending]; + [pf refreshPFRules]; + appendMode = NO; +} + - (void)finalizeBlock { + NSLog(@"About to run operation queue..."); [opQueue waitUntilAllOperationsAreFinished]; + NSLog(@"Operation queue ran!"); if(hostsBlockingEnabled) { [hostsBlocker addSelfControlBlockFooter]; @@ -83,84 +113,63 @@ - (void)finalizeBlock { [pf startBlock]; } -- (void)enqueueBlockEntryWithHostName:(NSString*)hostName port:(int)portNum maskLen:(int)maskLen { +- (void)enqueueBlockEntry:(SCBlockEntry*)entry { + NSLog(@"enqueue entry %@", entry); NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{ - [self addBlockEntryWithHostName: hostName port: portNum maskLen: maskLen]; + [self addBlockEntry: entry]; }]; [opQueue addOperation: op]; } -- (void)addBlockEntryWithHostName:(NSString*)hostName port:(int)portNum maskLen:(int)maskLen { - BOOL isIP = [hostName isValidIPAddress]; - BOOL isIPv4 = [hostName isValidIPv4Address]; +- (void)addBlockEntry:(SCBlockEntry*)entry { + // nil entries = something didn't parse right + if (entry == nil) return; + + BOOL isIP = [entry.hostname isValidIPAddress]; + BOOL isIPv4 = [entry.hostname isValidIPv4Address]; - if([hostName isEqualToString: @"*"]) { - [pf addRuleWithIP: nil port: portNum maskLen: 0]; + if([entry.hostname isEqualToString: @"*"]) { + [pf addRuleWithIP: nil port: entry.port maskLen: 0]; } else if(isIPv4) { // current we do NOT do ipfw blocking for IPv6 - [pf addRuleWithIP: hostName port: portNum maskLen: maskLen]; - } else if(!isIP && (![self domainIsGoogle: hostName] || isAllowlist)) { // domain name + [pf addRuleWithIP: entry.hostname port: entry.port maskLen: entry.maskLen]; + } else if(!isIP && (![self domainIsGoogle: entry.hostname] || isAllowlist)) { // domain name // on blocklist blocks where the domain is Google, we don't use ipfw to block // because we'd end up blocking more than the user wants (i.e. Search/Reader) - NSArray* addresses = [self ipAddressesForDomainName: hostName]; + NSArray* addresses = [self ipAddressesForDomainName: entry.hostname]; for(int i = 0; i < [addresses count]; i++) { NSString* ip = addresses[i]; - [pf addRuleWithIP: ip port: portNum maskLen: maskLen]; + [pf addRuleWithIP: ip port: entry.port maskLen: entry.maskLen]; } } - if(hostsBlockingEnabled && ![hostName isEqualToString: @"*"] && !portNum && !isIP) { - [hostsBlocker addRuleBlockingDomain: hostName]; + if(hostsBlockingEnabled && ![entry.hostname isEqualToString: @"*"] && !entry.port && !isIP) { + if (appendMode) { + [hostsBlocker appendExistingBlockWithRuleForDomain: entry.hostname]; + } else { + [hostsBlocker addRuleBlockingDomain: entry.hostname]; + } } } -- (void)addBlockEntryFromString:(NSString*)entry { - // don't do anything with blank hostnames, however they got on the list... - // otherwise they could end up screwing up the block - if (![[entry stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) { - return; - } - - NSDictionary* hostInfo = [self parseHostString: entry]; - - NSString* hostName = hostInfo[@"hostName"]; - NSNumber* portNumObject = hostInfo[@"port"]; - NSNumber* maskLenObject = hostInfo[@"maskLen"]; - int portNum = portNumObject ? [portNumObject intValue] : 0; - int maskLen = maskLenObject ? [maskLenObject intValue] : 0; - - // we won't block host * (everywhere) without a port number... it's just too likely to be mistaken. - // Use a allowlist if that's what you want! - if ([hostName isEqualToString: @"*"] && !portNum) { - return; - } - - [self addBlockEntryWithHostName: hostName port: portNum maskLen: maskLen]; - - if (isAllowlist && includeLinkedDomains && ![hostName isValidIPAddress]) { - NSSet* relatedDomains = [AllowlistScraper relatedDomains: hostName]; - [relatedDomains enumerateObjectsUsingBlock:^(NSString* host, BOOL* stop) { - [self enqueueBlockEntryWithHostName: host port: 0 maskLen: 0]; - }]; - } +- (void)addBlockEntryFromString:(NSString*)entryString { + NSLog(@"adding block entry from string: %@", entryString); + SCBlockEntry* entry = [SCBlockEntry entryFromString: entryString]; - if(![hostName isValidIPAddress] && includeCommonSubdomains) { - NSArray* commonSubdomains = [self commonSubdomainsForHostName: hostName]; + // nil means that we don't have anything valid to block in this entry + if (entry == nil) return; - for(int i = 0; i < [commonSubdomains count]; i++) { - // we do not pull port, we leave the port number the same as we got it - hostInfo = [self parseHostString: commonSubdomains[i]]; - hostName = hostInfo[@"hostName"]; - maskLenObject = hostInfo[@"maskLen"]; - maskLen = maskLenObject ? [maskLenObject intValue] : 0; - - [self enqueueBlockEntryWithHostName: hostName port: portNum maskLen: maskLen]; - } - } + [self addBlockEntry: entry]; + + NSArray* relatedEntries = [self relatedBlockEntriesForEntry: entry]; + NSLog(@"Enqueuing related entries to %@: %@", entry, relatedEntries); + for (SCBlockEntry* relatedEntry in relatedEntries) { + [self enqueueBlockEntry: relatedEntry]; + } } -- (void)addBlockEntries:(NSArray*)blockList { +- (void)addBlockEntriesFromStrings:(NSArray*)blockList { for(int i = 0; i < [blockList count]; i++) { NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{ [self addBlockEntryFromString: blockList[i]]; @@ -263,6 +272,7 @@ - (NSArray*)commonSubdomainsForHostName:(NSString*)hostName { // pulled list of facebook IP ranges from https://developers.facebook.com/docs/sharing/webmasters/crawler // TODO: pull these automatically by running: // whois -h whois.radb.net -- '-i origin AS32934' | grep ^route + // (looks like they now use 2 different AS numbers: https://www.facebook.com/peering/) NSArray* facebookIPs = @[@"31.13.24.0/21", @"31.13.64.0/18", @"45.64.40.0/22", @@ -329,48 +339,31 @@ - (BOOL)domainIsGoogle:(NSString*)domainName { return [googleTester evaluateWithObject: domainName]; } -- (NSDictionary*)parseHostString:(NSString*)hostString { - NSMutableDictionary* dict = [NSMutableDictionary dictionary]; - NSString* hostName; - - NSArray* splitString = [hostString componentsSeparatedByString: @"/"]; - hostName = splitString[0]; - - NSString* stringToSearchForPort = splitString[0]; - - if([splitString count] >= 2) { - int maskLen = [splitString[1] intValue]; - - if(maskLen != 0) { // 0 means we could not parse to int value - [dict setValue: @(maskLen) forKey: @"maskLen"]; - } - - // we expect the port number to come after the IP/masklen - stringToSearchForPort = splitString[1]; - } - - splitString = [stringToSearchForPort componentsSeparatedByString: @":"]; - - // only if hostName wasn't already split off by the maskLen - if([stringToSearchForPort isEqualToString: hostName]) { - hostName = splitString[0]; - } - - if([splitString count] >= 2) { - int portNum = [splitString[1] intValue]; - - if(portNum != 0) { // 0 means we could not parse to int value - [dict setValue: @(portNum) forKey: @"port"]; - } - } - - if([hostName isEqualToString: @""]) { - hostName = @"*"; - } - - [dict setValue: hostName forKey: @"hostName"]; - - return dict; +- (NSArray*)relatedBlockEntriesForEntry:(SCBlockEntry*)entry { + // nil means that we don't have anything valid to block in this entry, therefore no related entries either + if (entry == nil) return @[]; + + NSMutableArray* relatedEntries = [NSMutableArray array]; + + if (isAllowlist && includeLinkedDomains && ![entry.hostname isValidIPAddress]) { + NSArray* scrapedEntries = [[AllowlistScraper relatedBlockEntries: entry.hostname] allObjects]; + [relatedEntries addObjectsFromArray: scrapedEntries]; + } + + if(![entry.hostname isValidIPAddress] && includeCommonSubdomains) { + NSArray* commonSubdomains = [self commonSubdomainsForHostName: entry.hostname]; + + for (NSString* subdomain in commonSubdomains) { + // we do not pull port, we leave the port number the same as we got it + SCBlockEntry* subdomainEntry = [SCBlockEntry entryFromString: subdomain]; + + if (subdomainEntry == nil) continue; + + [relatedEntries addObject: subdomainEntry]; + } + } + + return relatedEntries; } @end diff --git a/HostFileBlocker.h b/Block Management/HostFileBlocker.h similarity index 93% rename from HostFileBlocker.h rename to Block Management/HostFileBlocker.h index fb5b51c8..7f2788ec 100755 --- a/HostFileBlocker.h +++ b/Block Management/HostFileBlocker.h @@ -30,6 +30,8 @@ NSFileManager* fileMan; } ++ (BOOL)blockFoundInHostsFile; + - (BOOL)deleteBackupHostsFile; - (void)revertFileContentsToDisk; @@ -45,6 +47,7 @@ - (BOOL)restoreBackupHostsFile; - (void)addRuleBlockingDomain:(NSString*)domainName; +- (void)appendExistingBlockWithRuleForDomain:(NSString*)domainName; - (BOOL)containsSelfControlBlock; diff --git a/HostFileBlocker.m b/Block Management/HostFileBlocker.m similarity index 74% rename from HostFileBlocker.m rename to Block Management/HostFileBlocker.m index 4c1da630..0c8b2aec 100755 --- a/HostFileBlocker.m +++ b/Block Management/HostFileBlocker.m @@ -52,11 +52,20 @@ - (HostFileBlocker*)init { return self; } ++ (BOOL)blockFoundInHostsFile { + // last try if we can't find a block anywhere: check the host file, and see if a block is in there + NSString* hostFileContents = [NSString stringWithContentsOfFile: kHostFileBlockerPath encoding: NSUTF8StringEncoding error: NULL]; + if(hostFileContents != nil && [hostFileContents rangeOfString: kHostFileBlockerSelfControlHeader].location != NSNotFound) { + return YES; + } + + return NO; +} + - (void)revertFileContentsToDisk { [strLock lock]; newFileContents = [NSMutableString stringWithContentsOfFile: kHostFileBlockerPath usedEncoding: &stringEnc error: NULL]; - if(!newFileContents) { newFileContents = [NSMutableString stringWithString: kDefaultHostsFileContents]; } @@ -118,13 +127,42 @@ - (void)addSelfControlBlockFooter { [strLock unlock]; } +- (NSArray*)ruleStringsToBlockDomain:(NSString*)domainName { + return @[ + [NSString stringWithFormat: @"0.0.0.0\t%@\n", domainName], + [NSString stringWithFormat: @"::\t%@\n", domainName] + ]; +} + - (void)addRuleBlockingDomain:(NSString*)domainName { [strLock lock]; - [newFileContents appendString: [NSString stringWithFormat: @"0.0.0.0\t%@\n", domainName]]; - [newFileContents appendString: [NSString stringWithFormat: @"::\t%@\n", domainName]]; + NSArray* ruleStrings = [self ruleStringsToBlockDomain: domainName]; + for (NSString* ruleString in ruleStrings) { + [newFileContents appendString: ruleString]; + } [strLock unlock]; } +- (void)appendExistingBlockWithRuleForDomain:(NSString*)domainName { + [strLock lock]; + NSRange footerLocation = [newFileContents rangeOfString: kHostFileBlockerSelfControlFooter]; + if (footerLocation.location == NSNotFound) { + // we can't append if a block isn't in the file already! + NSLog(@"WARNING: can't append to host block because footer can't be found"); + } else { + // combine the rule strings and insert em all at once to make the math easier + NSArray* ruleStrings = [self ruleStringsToBlockDomain: domainName]; + + NSMutableString* combinedRuleString = [NSMutableString string]; + for (NSString* ruleString in ruleStrings) { + [combinedRuleString appendString: ruleString]; + } + + [newFileContents insertString: combinedRuleString atIndex: footerLocation.location]; + } + [strLock unlock]; +} + - (BOOL)containsSelfControlBlock { [strLock lock]; diff --git a/HostImporter.h b/Block Management/HostImporter.h similarity index 100% rename from HostImporter.h rename to Block Management/HostImporter.h diff --git a/HostImporter.m b/Block Management/HostImporter.m similarity index 100% rename from HostImporter.m rename to Block Management/HostImporter.m diff --git a/PacketFilter.h b/Block Management/PacketFilter.h similarity index 86% rename from PacketFilter.h rename to Block Management/PacketFilter.h index 8d731029..680d5c79 100644 --- a/PacketFilter.h +++ b/Block Management/PacketFilter.h @@ -8,6 +8,8 @@ #import +@class SCBlockEntry; + @interface PacketFilter : NSObject { NSMutableString* rules; BOOL isAllowlist; @@ -22,5 +24,8 @@ - (int)stopBlock:(BOOL)force; - (void)addSelfControlConfig; - (BOOL)containsSelfControlBlock; +- (void)enterAppendMode; +- (void)finishAppending; +- (int)refreshPFRules; @end diff --git a/PacketFilter.m b/Block Management/PacketFilter.m similarity index 58% rename from PacketFilter.m rename to Block Management/PacketFilter.m index 1bbd3db5..1ef59b62 100644 --- a/PacketFilter.m +++ b/Block Management/PacketFilter.m @@ -12,6 +12,8 @@ @implementation PacketFilter +NSFileHandle* appendFileHandle; + - (PacketFilter*)initAsAllowlist: (BOOL)allowlist { if (self = [super init]) { isAllowlist = allowlist; @@ -47,32 +49,46 @@ - (void)addAllowlistFooter:(NSMutableString*)configText { [configText appendString: @"pass out proto tcp from any to any port 68\n"]; } +- (NSArray*)ruleStringsForIP:(NSString*)ip port:(int)port maskLen:(int)maskLen { + NSMutableString* rule = [NSMutableString stringWithString: @"from any to "]; + + if (ip) { + [rule appendString: ip]; + } else { + [rule appendString: @"any"]; + } + + if (maskLen) { + [rule appendString: [NSString stringWithFormat: @"/%d", maskLen]]; + } + + if (port) { + [rule appendString: [NSString stringWithFormat: @" port %d", port]]; + } + + if (isAllowlist) { + return @[ + [NSString stringWithFormat: @"pass out proto tcp %@\n", rule], + [NSString stringWithFormat: @"pass out proto udp %@\n", rule] + ]; + } else { + return @[ + [NSString stringWithFormat: @"block return out proto tcp %@\n", rule], + [NSString stringWithFormat: @"block return out proto udp %@\n", rule] + ]; + } +} - (void)addRuleWithIP:(NSString*)ip port:(int)port maskLen:(int)maskLen { - NSMutableString* rule = [NSMutableString stringWithString: @"from any to "]; - - if (ip) { - [rule appendString: ip]; - } else { - [rule appendString: @"any"]; - } - - if (maskLen) { - [rule appendString: [NSString stringWithFormat: @"/%d", maskLen]]; - } - - if (port) { - [rule appendString: [NSString stringWithFormat: @" port %d", port]]; - } - - @synchronized(self) { - if (isAllowlist) { - [rules appendString: [NSString stringWithFormat: @"pass out proto tcp %@\n", rule]]; - [rules appendString: [NSString stringWithFormat: @"pass out proto udp %@\n", rule]]; - } else { - [rules appendString: [NSString stringWithFormat: @"block return out proto tcp %@\n", rule]]; - [rules appendString: [NSString stringWithFormat: @"block return out proto udp %@\n", rule]]; - } - } + @synchronized(self) { + NSArray* ruleStrings = [self ruleStringsForIP: ip port: port maskLen: maskLen]; + for (NSString* ruleString in ruleStrings) { + if (appendFileHandle) { + [appendFileHandle writeData: [ruleString dataUsingEncoding:NSUTF8StringEncoding]]; + } else { + [rules appendString: ruleString]; + } + } + } } - (void)writeConfiguration { @@ -88,6 +104,56 @@ - (void)writeConfiguration { [filterConfiguration writeToFile: @"/etc/pf.anchors/org.eyebeam" atomically: true encoding: NSUTF8StringEncoding error: nil]; } +- (void)enterAppendMode { + if (isAllowlist) { + NSLog(@"WARNING: Can't append rules to allowlist blocks - ignoring"); + return; + } + + // open the file and prepare to write to the very bottom (no footer since it's not an allowlist) + appendFileHandle = [NSFileHandle fileHandleForWritingAtPath: @"/etc/pf.anchors/org.eyebeam"]; + if (!appendFileHandle) { + NSLog(@"ERROR: Failed to get handle for pf.anchors file while attempting to append rules"); + return; + } + + [appendFileHandle seekToEndOfFile]; +} +- (void)finishAppending { + [appendFileHandle closeFile]; + appendFileHandle = nil; +} + +- (void)appendRulesToCurrentBlockConfiguration:(NSArray*)newEntryDicts { + if (newEntryDicts.count < 1) return; + if (isAllowlist) { + NSLog(@"WARNING: Can't append rules to allowlist blocks - ignoring"); + return; + } + + // open the file and prepare to write to the very bottom (no footer since it's not an allowlist) + // NOTE FOR FUTURE: NSFileHandle can't append lines to the middle of the file anyway, + // would need to read in the whole thing + write out again + NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingAtPath: @"/etc/pf.anchors/org.eyebeam"]; + if (!fileHandle) { + NSLog(@"ERROR: Failed to get handle for pf.anchors file while attempting to append rules"); + return; + } + + [fileHandle seekToEndOfFile]; + for (NSDictionary* entryHostInfo in newEntryDicts) { + NSString* hostName = entryHostInfo[@"hostName"]; + int portNum = [entryHostInfo[@"port"] intValue]; + int maskLen = [entryHostInfo[@"maskLen"] intValue]; + + NSArray* ruleStrings = [self ruleStringsForIP: hostName port: portNum maskLen: maskLen]; + for (NSString* ruleString in ruleStrings) { + [fileHandle writeData: [ruleString dataUsingEncoding:NSUTF8StringEncoding]]; + } + } + [fileHandle closeFile]; +} + - (int)startBlock { [self addSelfControlConfig]; [self writeConfiguration]; @@ -118,6 +184,17 @@ - (int)startBlock { return [task terminationStatus]; } +- (int)refreshPFRules { + NSArray* args = [@"-f /etc/pf.conf -F states" componentsSeparatedByString: @" "]; + + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath: kPfctlExecutablePath]; + [task setArguments: args]; + [task launch]; + [task waitUntilExit]; + + return [task terminationStatus]; +} - (void)writePFToken:(NSString*)token error:(NSError**)error { [token writeToFile: @"/etc/SelfControlPFToken" atomically: YES encoding: NSUTF8StringEncoding error: error]; @@ -170,7 +247,7 @@ - (void)addSelfControlConfig { - (BOOL)containsSelfControlBlock { NSString* mainConf = [NSString stringWithContentsOfFile: @"/etc/pf.conf" encoding: NSUTF8StringEncoding error: nil]; - return [mainConf rangeOfString: @"org.eyebeam"].location != NSNotFound; + return mainConf != nil && [mainConf rangeOfString: @"org.eyebeam"].location != NSNotFound; } @end diff --git a/Block Management/SCBlockEntry.h b/Block Management/SCBlockEntry.h new file mode 100644 index 00000000..78e12358 --- /dev/null +++ b/Block Management/SCBlockEntry.h @@ -0,0 +1,26 @@ +// +// SCBlockEntry.h +// SelfControl +// +// Created by Charlie Stigler on 1/20/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCBlockEntry : NSObject + +@property (nonatomic) NSString* hostname; +@property (nonatomic) int port; +@property (nonatomic) int maskLen; + ++ (instancetype)entryWithHostname:(NSString*)hostname; ++ (instancetype)entryWithHostname:(NSString*)hostname port:(int)port maskLen:(int)maskLen; ++ (instancetype)entryFromString:(NSString*)domainString; + +- (BOOL)isEqualToEntry:(SCBlockEntry*)otherEntry; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Block Management/SCBlockEntry.m b/Block Management/SCBlockEntry.m new file mode 100644 index 00000000..ca8ae40c --- /dev/null +++ b/Block Management/SCBlockEntry.m @@ -0,0 +1,124 @@ +// +// SCBlockEntry.m +// SelfControl +// +// Created by Charlie Stigler on 1/20/21. +// + +#import "SCBlockEntry.h" + +@implementation SCBlockEntry + +- (instancetype)init { + return [self initWithHostname: nil port: 0 maskLen: 0]; +} +- (instancetype)initWithHostname:(NSString*)hostname { + return [self initWithHostname: hostname port: 0 maskLen: 0]; +} +- (instancetype)initWithHostname:(NSString*)hostname port:(int)port maskLen:(int)maskLen { + if (self = [super init]) { + _hostname = hostname; + _port = port; + _maskLen = maskLen; + } + return self; +} + ++ (instancetype)entryWithHostname:(NSString*)hostname port:(int)port maskLen:(int)maskLen { + return [[SCBlockEntry alloc] initWithHostname: hostname port: port maskLen: maskLen]; +} + ++ (instancetype)entryWithHostname:(NSString*)hostname { + return [[SCBlockEntry alloc] initWithHostname: hostname]; +} + ++ (instancetype)entryFromString:(NSString*)hostString { + // don't do anything with blank hostnames, however they got on the list... + // otherwise they could end up screwing up the block + if (![[hostString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) { + return nil; + } + + NSString* hostname; + // returning 0 for either of these values means "couldn't find it in the string" + int maskLen = 0; + int port = 0; + + NSArray* splitString = [hostString componentsSeparatedByString: @"/"]; + hostname = splitString[0]; + + NSString* stringToSearchForPort = splitString[0]; + + if([splitString count] >= 2) { + maskLen = [splitString[1] intValue]; + + // we expect the port number to come after the IP/masklen + stringToSearchForPort = splitString[1]; + } + + splitString = [stringToSearchForPort componentsSeparatedByString: @":"]; + + // only if hostName wasn't already split off by the maskLen + if([stringToSearchForPort isEqualToString: hostname]) { + hostname = splitString[0]; + } + + if([splitString count] >= 2) { + port = [splitString[1] intValue]; + } + + if([hostname isEqualToString: @""]) { + hostname = @"*"; + } + + // we won't block host * (everywhere) without a port number... it's just too likely to be mistaken. + // Use a allowlist if that's what you want! + if ([hostname isEqualToString: @"*"] && !port) { + return nil; + } + + return [SCBlockEntry entryWithHostname: hostname port: port maskLen: maskLen]; +} + +- (NSString*)description { + return [NSString stringWithFormat: @"[Entry: hostname = %@, port = %d, maskLen = %d]", self.hostname, self.port, self.maskLen]; +} + +// method implementations of isEqual, isEqualToEntry, and hash are based on this answer from StackOverflow: https://stackoverflow.com/q/254281 + +- (BOOL)isEqual:(id)other { + if (other == self) + return YES; + if (!other || ![other isKindOfClass: [self class]]) + return NO; + return [self isEqualToEntry: other]; +} + +- (BOOL)isEqualToEntry:(SCBlockEntry*)otherEntry { + if (otherEntry == nil) return NO; + if (self == otherEntry) return YES; + + if ([self.hostname isEqualToString: otherEntry.hostname] && self.port == otherEntry.port && self.maskLen == otherEntry.maskLen) { + return YES; + } else { + return NO; + } +} + +- (NSUInteger)hash { + NSUInteger prime = 31; + NSUInteger result = 1; + + if (self.hostname == nil) { + result = prime * result; + } else { + result = prime * result + [self.hostname hash]; + } + + result = prime * result + self.port; + result = prime * result + self.maskLen; + + return result; +} + +@end diff --git a/ThunderbirdPreferenceParser.h b/Block Management/ThunderbirdPreferenceParser.h similarity index 100% rename from ThunderbirdPreferenceParser.h rename to Block Management/ThunderbirdPreferenceParser.h diff --git a/ThunderbirdPreferenceParser.m b/Block Management/ThunderbirdPreferenceParser.m similarity index 100% rename from ThunderbirdPreferenceParser.m rename to Block Management/ThunderbirdPreferenceParser.m diff --git a/Common/DeprecationSilencers.h b/Common/DeprecationSilencers.h new file mode 100644 index 00000000..949b4df7 --- /dev/null +++ b/Common/DeprecationSilencers.h @@ -0,0 +1,23 @@ +// +// DeprecationSilencers.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#ifndef DeprecationSilencers_h +#define DeprecationSilencers_h + +// great idea taken from this guy: https://stackoverflow.com/a/26564750 + +#define SILENCE_DEPRECATION(expr) \ +do { \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ +expr; \ +_Pragma("clang diagnostic pop") \ +} while(0) + +#define SILENCE_OSX10_10_DEPRECATION(expr) SILENCE_DEPRECATION(expr) + +#endif /* DeprecationSilencers_h */ diff --git a/Common/SCBlockFileReaderWriter.h b/Common/SCBlockFileReaderWriter.h new file mode 100644 index 00000000..742c5345 --- /dev/null +++ b/Common/SCBlockFileReaderWriter.h @@ -0,0 +1,27 @@ +// +// SCBlockFileReaderWriter.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// read and write saved .selfcontrol block files +@interface SCBlockFileReaderWriter : NSObject + +// Writes out a saved .selfcontrol blocklist file to the file system +// containing the block info (blocklist + whitelist setting) defined +// in blockInfo. ++ (BOOL)writeBlocklistToFileURL:(NSURL*)targetFileURL blockInfo:(NSDictionary*)blockInfo error:(NSError*_Nullable*_Nullable)errRef; + +// reads in a saved .selfcontrol blocklist file and returns +// an NSDictionary with the block settings contained +// (properties are Blocklist and BlockAsWhitelist) ++ (NSDictionary*)readBlocklistFromFile:(NSURL*)fileURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/SCBlockFileReaderWriter.m b/Common/SCBlockFileReaderWriter.m new file mode 100644 index 00000000..cdcd8e27 --- /dev/null +++ b/Common/SCBlockFileReaderWriter.m @@ -0,0 +1,48 @@ +// +// SCBlockFileReaderWriter.m +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import "SCBlockFileReaderWriter.h" + +@implementation SCBlockFileReaderWriter + ++ (BOOL)writeBlocklistToFileURL:(NSURL*)targetFileURL blockInfo:(NSDictionary*)blockInfo error:(NSError**)errRef { + NSDictionary* saveDict = @{@"HostBlacklist": [blockInfo objectForKey: @"Blocklist"], + @"BlockAsWhitelist": [blockInfo objectForKey: @"BlockAsWhitelist"]}; + + NSData* saveData = [NSPropertyListSerialization dataWithPropertyList: saveDict format: NSPropertyListBinaryFormat_v1_0 options: 0 error: errRef]; + if (*errRef != nil) { + return NO; + } + + if (![saveData writeToURL: targetFileURL atomically: YES]) { + NSLog(@"ERROR: Failed to write blocklist to URL %@", targetFileURL); + *errRef = [SCErr errorWithCode: 106]; + return NO; + } + + // for prettiness sake, attempt to hide the file extension + NSDictionary* attribs = @{NSFileExtensionHidden: @YES}; + [[NSFileManager defaultManager] setAttributes: attribs ofItemAtPath: [targetFileURL path] error: errRef]; + + return YES; +} + ++ (NSDictionary*)readBlocklistFromFile:(NSURL*)fileURL { + NSDictionary* openedDict = [NSDictionary dictionaryWithContentsOfURL: fileURL]; + + if (openedDict == nil || openedDict[@"HostBlacklist"] == nil || openedDict[@"BlockAsWhitelist"] == nil) { + NSLog(@"ERROR: Could not read a valid block from file %@", fileURL); + return nil; + } + + return @{ + @"Blocklist": openedDict[@"HostBlacklist"], + @"BlockAsWhitelist": openedDict[@"BlockAsWhitelist"] + }; +} + +@end diff --git a/Common/SCErr.h b/Common/SCErr.h new file mode 100644 index 00000000..e5f0db95 --- /dev/null +++ b/Common/SCErr.h @@ -0,0 +1,25 @@ +// +// SCErr.h +// SelfControl +// +// Created by Charlie Stigler on 1/13/21. +// + +#import + +// copied from StackOverflow answer by Wolfgang Schreurs: https://stackoverflow.com/a/14086231 +#define SC_ERROR_KEY(code) [NSString stringWithFormat:@"%d", code] +#define SC_ERROR_LOCALIZED_DESCRIPTION(code) NSLocalizedStringFromTable(SC_ERROR_KEY(code), @"SCError", nil) + +FOUNDATION_EXPORT NSString * _Nonnull const kSelfControlErrorDomain; + +NS_ASSUME_NONNULL_BEGIN + +@interface SCErr : NSObject + ++ (NSError*)errorWithCode:(int)errorCode subDescription:(NSString* _Nullable)subDescription; ++ (NSError*)errorWithCode:(int)errorCode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/SCErr.m b/Common/SCErr.m new file mode 100644 index 00000000..ad08785f --- /dev/null +++ b/Common/SCErr.m @@ -0,0 +1,31 @@ +// +// SCErr.m +// SelfControl +// +// Created by Charlie Stigler on 1/13/21. +// + +#import "SCErr.h" + +NSString *const kSelfControlErrorDomain = @"SelfControlErrorDomain"; + +@implementation SCErr + ++ (NSError*)errorWithCode:(int)errorCode subDescription:(NSString * _Nullable )subDescription { + NSString* description = SC_ERROR_LOCALIZED_DESCRIPTION(errorCode); + if (subDescription != nil) { + description = [NSString stringWithFormat: description, subDescription]; + } + + return [NSError errorWithDomain: kSelfControlErrorDomain + code: errorCode + userInfo: @{ + NSLocalizedDescriptionKey: description + }]; +} + ++ (NSError*)errorWithCode:(int)errorCode { + return [SCErr errorWithCode: errorCode subDescription: nil]; +} + +@end diff --git a/Common/SCSentry.h b/Common/SCSentry.h new file mode 100644 index 00000000..f48b3caa --- /dev/null +++ b/Common/SCSentry.h @@ -0,0 +1,25 @@ +// +// SCSentry.h +// SelfControl +// +// Created by Charlie Stigler on 1/15/21. +// + +#import + +@class SentryScope; + +NS_ASSUME_NONNULL_BEGIN + +@interface SCSentry : NSObject + ++ (void)startSentry:(NSString*)componentId; ++ (void)addBreadcrumb:(NSString*)message category:(NSString*)category; ++ (void)captureError:(NSError*)error; ++ (void)captureMessage:(NSString*)message withScopeBlock:(nullable void (^)(SentryScope * _Nonnull))block; ++ (void)captureMessage:(NSString*)message; ++ (BOOL)showErrorReportingPromptIfNeeded; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/SCSentry.m b/Common/SCSentry.m new file mode 100644 index 00000000..58717d2b --- /dev/null +++ b/Common/SCSentry.m @@ -0,0 +1,185 @@ +// +// SCSentry.m +// SelfControl +// +// Created by Charlie Stigler on 1/15/21. +// + +#import "SCSentry.h" +#import "SCSettings.h" + +#ifndef TESTING +#import +#endif + +@implementation SCSentry + +//org.eyebeam.SelfControl ++ (void)startSentry:(NSString*)componentId { +#ifndef TESTING + [SentrySDK startWithConfigureOptions:^(SentryOptions *options) { + options.dsn = @"https://58fbe7145368418998067f88896007b2@o504820.ingest.sentry.io/5592195"; + options.debug = YES; // Enabled debug when first installing is always helpful + options.releaseName = [NSString stringWithFormat: @"%@%@", componentId, SELFCONTROL_VERSION_STRING]; + options.enableAutoSessionTracking = NO; + options.environment = @"dev"; + + // make sure no data leaves the device if error reporting isn't enabled + options.beforeSend = ^SentryEvent * _Nullable(SentryEvent * _Nonnull event) { + if ([SCSentry errorReportingEnabled]) { + return event; + } else { + return NULL; + } + }; + }]; + [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { + [scope setTagValue: [[NSLocale currentLocale] localeIdentifier] forKey: @"localeId"]; + }]; +#endif +} + ++ (BOOL)errorReportingEnabled { +#ifdef TESTING + // don't report to Sentry while unit-testing! + if ([[NSUserDefaults standardUserDefaults] boolForKey: @"isTest"]) { + return YES; + } +#endif + if (geteuid() != 0) { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + return [defaults boolForKey: @"EnableErrorReporting"]; + } else { + // since we're root, we've gotta see what's in SCSettings (where the user's defaults will have been copied) + return [[SCSettings sharedSettings] boolForKey: @"EnableErrorReporting"]; + } +} + +// returns YES if we turned on error reporting based on the prompt return ++ (BOOL)showErrorReportingPromptIfNeeded { + // no need to show the prompt if we're root (aka in the CLI/daemon), or already enabled error reporting, or if the user already dismissed it + if (!geteuid()) return NO; + if ([SCSentry errorReportingEnabled]) return NO; + + // if they've already dismissed this once, don't show it again + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + if ([defaults boolForKey: @"ErrorReportingPromptDismissed"]) { + return NO; + } + + // all UI stuff MUST be done on the main thread + if (![NSThread isMainThread]) { + __block BOOL retVal = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ + retVal = [SCSentry showErrorReportingPromptIfNeeded]; + }); + return retVal; + } + + NSAlert* alert = [[NSAlert alloc] init]; + [alert setMessageText: NSLocalizedString(@"Enable automatic error reporting", "Title of error reporting prompt")]; + [alert setInformativeText:NSLocalizedString(@"SelfControl can automatically send bug reports to help us improve the software. All data is anonymized, your blocklist is never shared, and no identifying information is sent.", @"Message explaining error reporting")]; + [alert addButtonWithTitle: NSLocalizedString(@"Enable Error Reporting", @"Button to enable error reporting")]; + [alert addButtonWithTitle: NSLocalizedString(@"Don't Send Reports", "Button to decline error reporting")]; + + NSModalResponse modalResponse = [alert runModal]; + if (modalResponse == NSAlertFirstButtonReturn) { + [defaults setBool: YES forKey: @"EnableErrorReporting"]; + [defaults setBool: YES forKey: @"ErrorReportingPromptDismissed"]; + return YES; + } else if (modalResponse == NSAlertSecondButtonReturn) { + [defaults setBool: NO forKey: @"EnableErrorReporting"]; + [defaults setBool: YES forKey: @"ErrorReportingPromptDismissed"]; + } // if the modal exited some other way, do nothing + + return NO; +} + ++ (void)updateDefaultsContext { + // if we're root, we can't get defaults properly, so forget it + if (!geteuid()) { + return; + } + + NSMutableDictionary* defaultsDict = [[[NSUserDefaults standardUserDefaults] persistentDomainForName: @"org.eyebeam.SelfControl"] mutableCopy]; + + // delete blocklist (because PII) and update check time + // (because unnecessary, and Sentry doesn't like dates) + // but store the blocklist length as a useful piece of debug info + id blocklist = defaultsDict[@"Blocklist"]; + NSUInteger blocklistLength = (blocklist == nil) ? 0 : ((NSArray*)blocklist).count; + [defaultsDict setObject: @(blocklistLength) forKey: @"BlocklistLength"]; + [defaultsDict removeObjectForKey: @"Blocklist"]; + [defaultsDict removeObjectForKey: @"SULastCheckTime"]; + +#ifndef TESTING + [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { + [scope setContextValue: defaultsDict forKey: @"NSUserDefaults"]; + }]; +#endif +} + ++ (void)addBreadcrumb:(NSString*)message category:(NSString*)category { +#ifndef TESTING + SentryBreadcrumb* crumb = [[SentryBreadcrumb alloc] init]; + crumb.level = kSentryLevelInfo; + crumb.category = category; + crumb.message = message; + [SentrySDK addBreadcrumb: crumb]; +#endif +} + ++ (void)captureError:(NSError*)error { + if (![SCSentry errorReportingEnabled]) { + // if we're root (CLI/daemon), we can't show prompts + if (!geteuid()) { + return; + } + + // prompt 'em to turn on error reports now if we haven't already! if they do we can continue + BOOL enabledReports = [SCSentry showErrorReportingPromptIfNeeded]; + if (!enabledReports) { + return; + } + } + + NSLog(@"Reporting error %@ to Sentry...", error); + [[SCSettings sharedSettings] updateSentryContext]; + [SCSentry updateDefaultsContext]; +#ifndef TESTING + [SentrySDK captureError: error]; +#endif +} + ++ (void)captureMessage:(NSString*)message withScopeBlock:(nullable void (^)(SentryScope * _Nonnull))block { + if (![SCSentry errorReportingEnabled]) { + // if we're root (CLI/daemon), we can't show prompts + if (!geteuid()) { + return; + } + + // prompt 'em to turn on error reports now if we haven't already! if they do we can continue + BOOL enabledReports = [SCSentry showErrorReportingPromptIfNeeded]; + if (!enabledReports) { + return; + } + } + + NSLog(@"Reporting message %@ to Sentry...", message); + [[SCSettings sharedSettings] updateSentryContext]; + [SCSentry updateDefaultsContext]; + +#ifndef TESTING + if (block != nil) { + [SentrySDK captureMessage: message withScopeBlock: block]; + } else { + [SentrySDK captureMessage: message]; + } +#endif +} + ++ (void)captureMessage:(NSString*)message { + [SCSentry captureMessage: message withScopeBlock: nil]; +} + +@end diff --git a/SCSettings.h b/Common/SCSettings.h similarity index 66% rename from SCSettings.h rename to Common/SCSettings.h index b7c435f2..26f87808 100644 --- a/SCSettings.h +++ b/Common/SCSettings.h @@ -6,7 +6,6 @@ // #import -#import "SelfControlCommon.h" NS_ASSUME_NONNULL_BEGIN @@ -14,22 +13,29 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly) uid_t userId; @property (readonly) NSDictionary* dictionaryRepresentation; +@property (nonatomic, getter=isReadOnly) BOOL readOnly; -+ (instancetype)currentUserSettings; -+ (instancetype)settingsForUser:(uid_t)uid; +@property (class, nonatomic, readonly) NSString* settingsFileName; +@property (class, nonatomic, readonly) NSString* securedSettingsFilePath; -- (NSString*)securedSettingsFilePath; ++ (instancetype)sharedSettings; - (void)reloadSettings; - (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))completionBlock; - (void)writeSettings; - (void)synchronizeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))completionBlock; - (void)synchronizeSettings; +- (NSError*)syncSettingsAndWait:(int)timeoutSecs; + - (void)setValue:(id)value forKey:(NSString*)key stopPropagation:(BOOL)stopPropagation; - (void)setValue:(nullable id)value forKey:(NSString*)key; + - (id)valueForKey:(NSString*)key; -- (void)migrateLegacySettings; -- (void)clearLegacySettings; +- (BOOL)boolForKey:(NSString*)key; + +- (void)updateSentryContext; + +- (void)resetAllSettingsToDefaults; @end diff --git a/SCSettings.m b/Common/SCSettings.m similarity index 58% rename from SCSettings.m rename to Common/SCSettings.m index f82bdd4c..81a20917 100644 --- a/SCSettings.m +++ b/Common/SCSettings.m @@ -6,14 +6,15 @@ // #import "SCSettings.h" -#include -#import -#include -#import "SCUtilities.h" #import +#ifndef TESTING +#import +#endif + float const SYNC_INTERVAL_SECS = 30; float const SYNC_LEEWAY_SECS = 30; +NSString* const SETTINGS_FILE_DIR = @"/usr/local/etc/"; @interface SCSettings () @@ -21,77 +22,28 @@ @interface SCSettings () @property (readonly) NSMutableDictionary* settingsDict; @property NSDate* lastSynchronizedWithDisk; @property dispatch_source_t syncTimer; +@property dispatch_source_t debouncedChangeTimer; @end @implementation SCSettings -/* TODO: move these two functions to a utility class */ - -// by Martin R et al on StackOverflow: https://stackoverflow.com/a/15451318 -- (NSString *)getSerialNumber { - NSString *serial = nil; - io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching("IOPlatformExpertDevice")); - if (platformExpert) { - CFTypeRef serialNumberAsCFString = - IORegistryEntryCreateCFProperty(platformExpert, - CFSTR(kIOPlatformSerialNumberKey), - kCFAllocatorDefault, 0); - if (serialNumberAsCFString) { - serial = CFBridgingRelease(serialNumberAsCFString); - } - - IOObjectRelease(platformExpert); - } - return serial; -} -// by hypercrypt et al on StackOverflow: https://stackoverflow.com/a/7571583 -- (NSString *)sha1:(NSString*)stringToHash -{ - NSData *data = [stringToHash dataUsingEncoding:NSUTF8StringEncoding]; - uint8_t digest[CC_SHA1_DIGEST_LENGTH]; - - CC_SHA1(data.bytes, (CC_LONG)data.length, digest); - - NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; - - for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) - { - [output appendFormat:@"%02x", digest[i]]; - } - - return output; -} -- (NSString*)homeDirectoryForUid:(uid_t)uid { - struct passwd *pwd = getpwuid(uid); - return [NSString stringWithCString: pwd->pw_dir encoding: NSString.defaultCStringEncoding]; -} - -+ (instancetype)settingsForUser:(uid_t)uid { - // on first run, set up a cache of SCSettings objects so can reuse the same one for a given user - static NSMutableDictionary* settingsForUserIds; ++ (instancetype)sharedSettings { + static SCSettings* globalSettings = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - settingsForUserIds = [NSMutableDictionary new]; + globalSettings = [SCSettings new]; }); - - @synchronized (settingsForUserIds) { - if (settingsForUserIds[@(uid)] == nil) { - // no settings object yet for this UID, instantiate one - settingsForUserIds[@(uid)] = [[self alloc] initWithUserId: uid]; - } - } - - // return the settings object we've got cached for this user id! - return settingsForUserIds[@(uid)]; -} -+ (instancetype)currentUserSettings { - return [SCSettings settingsForUser: getuid()]; + return globalSettings; } -- (instancetype)initWithUserId:(uid_t)userId { + +- (instancetype)init { if (self = [super init]) { - _userId = userId; + // we will only write out settings if we have root permissions (i.e. the EUID is 0) + // otherwise, we won't/shouldn't have permissions to write to the settings file + // in practice, what this means is that the daemon writes settings, and the app/CLI only read + _readOnly = (geteuid() != 0); + _settingsDict = nil; [[NSDistributedNotificationCenter defaultCenter] addObserver: self @@ -103,28 +55,50 @@ - (instancetype)initWithUserId:(uid_t)userId { return self; } ++ (NSString*)settingsFileName { + static NSString* fileName = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fileName = [NSString stringWithFormat: @".%@.plist", [SCMiscUtilities sha1: [NSString stringWithFormat: @"SelfControlUserPreferences%@", [SCMiscUtilities getSerialNumber]]]]; + }); -- (NSString*)securedSettingsFilePath { - NSString* homeDir = [self homeDirectoryForUid: self.userId]; - NSString* hash = [self sha1: [NSString stringWithFormat: @"SelfControlUserPreferences%@", [self getSerialNumber]]]; - return [[NSString stringWithFormat: @"%@/Library/Preferences/.%@.plist", homeDir, hash] stringByExpandingTildeInPath]; + return fileName; +} ++ (NSString*)securedSettingsFilePath { + static NSString* filePath = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + filePath = [NSString stringWithFormat: @"%@%@", SETTINGS_FILE_DIR, SCSettings.settingsFileName]; + }); + + return filePath; } // NOTE: there should be a default setting for each valid setting, even if it's nil/zero/etc - (NSDictionary*)defaultSettingsDict { return @{ @"BlockEndDate": [NSDate distantPast], - @"Blocklist": @[], + @"ActiveBlocklist": @[], + @"ActiveBlockAsWhitelist": @NO, + + @"BlockIsRunning": @NO, // tells us whether a block is actually running on the system (to the best of our knowledge) + @"TamperingDetected": @NO, + + // block settings + // the user sets these in defaults, then when a block is started they're copied over to settings @"EvaluateCommonSubdomains": @YES, @"IncludeLinkedDomains": @YES, @"BlockSoundShouldPlay": @NO, @"BlockSound": @5, @"ClearCaches": @YES, - @"BlockAsWhitelist": @NO, @"AllowLocalNetworks": @YES, - @"BlockIsRunning": @NO, // tells us whether a block is actually running on the system (to the best of our knowledge) - - @"TamperingDetected": @NO, + // why default error reporting on here, but off in defaults? + // generally, error reporting in settings should _always_ be copied by defaults + // if the default settings are used, that's an error that we want to report + // and obviously we can't trust the EnableErrorReporting setting at that point! + @"EnableErrorReporting": @YES, @"SettingsVersionNumber": @0, @"LastSettingsUpdate": [NSDate distantPast] // special value that keeps track of when we last updated our settings @@ -135,26 +109,30 @@ - (void)initializeSettingsDict { // make sure we only load the settings dictionary once, even if called simultaneously from multiple threads static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - self->_settingsDict = [NSMutableDictionary dictionaryWithContentsOfFile: [self securedSettingsFilePath]]; - - BOOL isTest = [[NSUserDefaults standardUserDefaults] boolForKey: @"isTest"]; - if (isTest) NSLog(@"Ignoring settings on disk because we're unit-testing"); - - // if we don't have a settings dictionary on disk yet, - // set it up with the default values (and migrate legacy settings also) - // also if we're running tests, just use the default dict - if (self->_settingsDict == nil || isTest) { - self->_settingsDict = [[self defaultSettingsDict] mutableCopy]; - [self migrateLegacySettings]; + @synchronized (self) { + self->_settingsDict = [NSMutableDictionary dictionaryWithContentsOfFile: SCSettings.securedSettingsFilePath]; + + BOOL isTest = [[NSUserDefaults standardUserDefaults] boolForKey: @"isTest"]; + if (isTest) NSLog(@"Ignoring settings on disk because we're unit-testing"); + + // if we don't have a settings dictionary on disk yet, + // set it up with the default values (and migrate legacy settings also) + // also if we're running tests, just use the default dict + if (self->_settingsDict == nil || isTest) { + self->_settingsDict = [[self defaultSettingsDict] mutableCopy]; + + // write out our brand-new settings to disk! + if (!self.readOnly) { + [self writeSettings]; + } + [SCSentry addBreadcrumb: @"Initialized SCSettings to default settings" category: @"settings"]; + } - // write out our brand-new migrated settings to disk! - [self writeSettings]; + // we're now current with disk! + self->lastSynchronizedWithDisk = [NSDate date]; + + [self startSyncTimer]; } - - // we're now current with disk! - self->lastSynchronizedWithDisk = [NSDate date]; - - [self startSyncTimer]; }); } @@ -191,7 +169,7 @@ - (void)reloadSettings { } @synchronized (self) { - NSDictionary* settingsFromDisk = [NSDictionary dictionaryWithContentsOfFile: [self securedSettingsFilePath]]; + NSDictionary* settingsFromDisk = [NSDictionary dictionaryWithContentsOfFile: SCSettings.securedSettingsFilePath]; int diskSettingsVersion = [settingsFromDisk[@"SettingsVersionNumber"] intValue]; int memorySettingsVersion = [[self valueForKey: @"SettingsVersionNumber"] intValue]; @@ -224,18 +202,28 @@ - (void)reloadSettings { _settingsDict = [settingsFromDisk mutableCopy]; self.lastSynchronizedWithDisk = [NSDate date]; NSLog(@"Newer SCSettings found on disk (version %d vs %d with time interval %f), updating...", diskSettingsVersion, memorySettingsVersion, [diskSettingsLastUpdated timeIntervalSinceDate: memorySettingsLastUpdated]); - + [SCSentry addBreadcrumb: @"Updated SCSettings to newer settings found on disk" category: @"settings"]; } } } - (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))completionBlock { @synchronized (self) { - if ([[NSUserDefaults standardUserDefaults] boolForKey: @"isTest"]) { - // no writing to disk during unit tests - NSLog(@"Would write settings to disk now (but no writing during unit tests)"); - if (completionBlock != nil) completionBlock(nil); + if (self.readOnly) { + NSLog(@"WARNING: Read-only SCSettings instance can't write out settings"); + NSError* err = [SCErr errorWithCode: 600]; + [SCSentry captureError: err]; + if (completionBlock != nil) { + completionBlock(err); + } return; } + +#if TESTING + // no writing to disk during unit tests + NSLog(@"Would write settings to disk now (but no writing during unit tests)"); + if (completionBlock != nil) completionBlock(nil); + return; +#endif // don't spend time on the main thread writing out files - it's OK for this to happen without blocking other things dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -250,9 +238,22 @@ - (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))comple if (completionBlock != nil) completionBlock(serializationErr); return; } + + NSError* createDirectoryErr; + BOOL createDirectorySuccessful = [[NSFileManager defaultManager] createDirectoryAtURL: [NSURL fileURLWithPath: SETTINGS_FILE_DIR] + withIntermediateDirectories: YES + attributes: @{ + NSFileOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFileGroupOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFilePosixPermissions: [NSNumber numberWithShort: 0755] + } + error: &createDirectoryErr]; + if (!createDirectorySuccessful) { + NSLog(@"WARNING: Failed to create %@ folder to store SCSettings. Error was %@", SETTINGS_FILE_DIR, createDirectoryErr); + } NSError* writeErr; - BOOL writeSuccessful = [plistData writeToFile: self.securedSettingsFilePath + BOOL writeSuccessful = [plistData writeToFile: SCSettings.securedSettingsFilePath options: NSDataWritingAtomic error: &writeErr ]; @@ -260,10 +261,11 @@ - (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))comple NSError* chmodErr; BOOL chmodSuccessful = [[NSFileManager defaultManager] setAttributes: @{ - @"NSFileOwnerAccountID": [NSNumber numberWithUnsignedLong: self.userId], - @"NSFilePosixPermissions": [NSNumber numberWithShort: 0755] + NSFileOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFileGroupOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFilePosixPermissions: [NSNumber numberWithShort: 0755] } - ofItemAtPath: self.securedSettingsFilePath + ofItemAtPath: SCSettings.securedSettingsFilePath error: &chmodErr]; if (writeSuccessful) { @@ -271,12 +273,15 @@ - (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))comple } if (!writeSuccessful) { - NSLog(@"Failed to write secured settings to file %@", self.securedSettingsFilePath); + NSLog(@"Failed to write secured settings to file %@", SCSettings.securedSettingsFilePath); + [SCSentry captureError: writeErr]; if (completionBlock != nil) completionBlock(writeErr); } else if (!chmodSuccessful) { - NSLog(@"Failed to change secured settings file owner/permissions secured settings for file %@ with error %@", self.securedSettingsFilePath, chmodErr); + NSLog(@"Failed to change secured settings file owner/permissions secured settings for file %@ with error %@", SCSettings.securedSettingsFilePath, chmodErr); + [SCSentry captureError: chmodErr]; if (completionBlock != nil) completionBlock(chmodErr); } else { + [SCSentry addBreadcrumb: @"Successfully wrote SCSettings out to file" category: @"settings"]; if (completionBlock != nil) completionBlock(nil); } }); @@ -302,18 +307,53 @@ - (void)synchronizeSettingsWithCompletion:(nullable void (^)(NSError * _Nullable [self setValue: [NSDate date] forKey: @"LastSettingsUpdate"]; } - if ([lastSettingsUpdate timeIntervalSinceDate: self.lastSynchronizedWithDisk] > 0) { + if ([lastSettingsUpdate timeIntervalSinceDate: self.lastSynchronizedWithDisk] > 0 && !self.readOnly) { NSLog(@" --> Writing settings to disk (haven't been written since %@)", self.lastSynchronizedWithDisk); [self writeSettingsWithCompletion: completionBlock]; } else { - if(completionBlock != nil) completionBlock(nil); + if(completionBlock != nil) { + // don't just run the callback asynchronously, since it makes this method harder to reason about + // (it'd sometimes call back synchronously and sometimes async) +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + completionBlock(nil); +// }); + } } } - (void)synchronizeSettings { [self synchronizeSettingsWithCompletion: nil]; } +- (NSError*)syncSettingsAndWait:(int)timeoutSecs { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + __block NSError* retErr = nil; + + // do this on another thread so it doesn't deadlock our semaphore + // (also dispatch_async ensures correct behavior even if synchronizeSettingsWithCompletion itself returns synchronously) + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self synchronizeSettingsWithCompletion:^(NSError* err) { + retErr = err; + + dispatch_semaphore_signal(sema); + }]; + }); + + if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, timeoutSecs * NSEC_PER_SEC))) { + retErr = [SCErr errorWithCode: 601]; + } + + return retErr; +} + - (void)setValue:(id)value forKey:(NSString*)key stopPropagation:(BOOL)stopPropagation { + // if we're a readonly instance, we generally shouldn't be allowing values to be set + // the only exception is receiving value updates (via notification) from other processes + // in which case stopPropagation will be true + if (self.readOnly && !stopPropagation) { + NSLog(@"WARNING: Read-only SCSettings instance can't update values (setting %@ to %@)", key, value); + return; + } + // we can't store nils in a dictionary // so we sneak around it if (value == nil) { @@ -339,7 +379,7 @@ - (void)setValue:(id)value forKey:(NSString*)key stopPropagation:(BOOL)stopPropa // notify other instances (presumably in other processes) // stopPropagation is a flag that stops one setting change from bouncing back and forth for ages - // between two processes + // between two processes. It indicates that the change started in another process if (!stopPropagation) { [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"org.eyebeam.SelfControl.SCSettingsValueChanged" object: self.description @@ -353,6 +393,7 @@ - (void)setValue:(id)value forKey:(NSString*)key stopPropagation:(BOOL)stopPropa ]; } } + - (void)setValue:(id)value forKey:(NSString*)key { [self setValue: value forKey: key stopPropagation: NO]; } @@ -372,80 +413,10 @@ - (id)valueForKey:(NSString*)key { return value; } - -// We might have "legacy" block settings hiding in one of two places: -// - a "lock file" at /etc/SelfControl.lock (aka SelfControlLegacyLockFilePath) -// - the defaults system -// we should check for block settings in both of these places and move them to the new SCSettings system -// (defaults continues to be used for some settings that only affect the UI and don't need to be read by helper tools) -// NOTE: this method should only be called when SCSettings is uninitialized, since it will overwrite any existing settings -// NOTE2: this method does NOT clear the settings from legacy locations, because that may break ongoing blocks being cleared -// by older versions of the helper tool. Instead, we will clean out legacy locations from the helper when -// blocks are started or finished. -// NOTE3: this method always pulls user defaults for the current user, regardless of what instance it's called on -- (void)migrateLegacySettings { - NSDictionary* lockDict = [NSDictionary dictionaryWithContentsOfFile: SelfControlLegacyLockFilePath]; - // note that the defaults will generally only be defined in the main app, not helper tool (because helper tool runs as root) - NSDictionary* userDefaultsDict = [NSUserDefaults standardUserDefaults].dictionaryRepresentation; - - // prefer reading from the lock file, using defaults as a backup only - for (NSString* key in [[self defaultSettingsDict] allKeys]) { - if (lockDict[key] != nil) { - [self setValue: lockDict[key] forKey: key]; - } else if (userDefaultsDict[key] != nil) { - [self setValue: userDefaultsDict[key] forKey: key]; - } - } - - // Blocklist attribute was renamed so needs a special migration - if (lockDict[@"HostBlacklist"] != nil) { - [self setValue: lockDict[@"HostBlacklist"] forKey: @"Blocklist"]; - } else if (userDefaultsDict[@"HostBlacklist"] != nil) { - [self setValue: userDefaultsDict[@"HostBlacklist"] forKey: @"Blocklist"]; - } - - // BlockStartedDate was migrated to a simpler BlockEndDate property (which doesn't require BlockDuration to function) - // so we need to specially convert the old BlockStartedDate into BlockEndDates - // NOTE: we do NOT set BlockIsRunning to YES in Settings for a legacy migration - // Why? the old version of the helper tool si still involved, and it doesn't know - // to clear that setting. So it will stay stuck on. - if ([SCUtilities blockIsRunningInLegacyDictionary: lockDict]) { - [self setValue: [SCUtilities endDateFromLegacyBlockDictionary: lockDict] forKey: @"BlockEndDate"]; - } else if ([SCUtilities blockIsRunningInDictionary: userDefaultsDict]) { - [self setValue: [SCUtilities endDateFromLegacyBlockDictionary: userDefaultsDict] forKey: @"BlockEndDate"]; - } +- (BOOL)boolForKey:(NSString*)key { + return [[self valueForKey: key] boolValue]; } -// NOTE: this method always clears the user defaults for the current user, regardless of what instance -// it's called on -- (void)clearLegacySettings { - // make sure the settings dictionary is set up (and migration has occurred if necessary) - [self initializeSettingsDict]; - - NSError* err; - - // no more need for the old lock file! - if(![[NSFileManager defaultManager] removeItemAtPath: SelfControlLegacyLockFilePath error: &err] && [[NSFileManager defaultManager] fileExistsAtPath: SelfControlLegacyLockFilePath]) { - NSLog(@"WARNING: Could not remove legacy SelfControl lock file because of error: %@", err); - } - - // clear keys out of user defaults which are now stored in SCSettings - NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; - NSArray* keysToClear = @[ - @"BlockStartedDate", - @"HostBlacklist", - @"EvaluateCommonSubdomains", - @"IncludeLinkedDomains", - @"BlockSoundShouldPlay", - @"BlockSound", - @"ClearCaches", - @"BlockAsWhitelist", - @"AllowLocalNetworks" - ]; - for (NSString* key in keysToClear) { - [userDefaults removeObjectForKey: key]; - } -} - (void)startSyncTimer { if (self.syncTimer != nil) { // we already have a timer, so no need to start another @@ -463,14 +434,51 @@ - (void)startSyncTimer { dispatch_resume(self.syncTimer); } } -- (void)cancelSyncTimer { - if (self.syncTimer == nil) { - // no active timer, no need to cancel - return; +- (void)cancelSyncTimers { + if (self.syncTimer != nil) { + dispatch_source_cancel(self.syncTimer); + self.syncTimer = nil; + } + + if (self.debouncedChangeTimer != nil) { + dispatch_source_cancel(self.debouncedChangeTimer); + self.debouncedChangeTimer = nil; + } +} + +- (void)updateSentryContext { + // make sure Sentry has the latest context in the event of a crash + + NSMutableDictionary* dictCopy = [self.settingsDict mutableCopy]; + + // fill in any gaps with default values (like we did if they called valueForKey:) + for (NSString* key in [[self defaultSettingsDict] allKeys]) { + if (dictCopy[key] == nil) { + dictCopy[key] = [self defaultSettingsDict][key]; + } + } + + // eliminate privacy-sensitive data (i.e. blocklist) + // but store the blocklist length as a useful piece of debug info + id activeBlocklist = dictCopy[@"ActiveBlocklist"]; + NSUInteger blocklistLength = (activeBlocklist == nil) ? 0 : ((NSArray*)activeBlocklist).count; + [dictCopy setObject: @(blocklistLength) forKey: @"ActiveBlocklistLength"]; + [dictCopy removeObjectForKey: @"Blocklist"]; + [dictCopy removeObjectForKey: @"ActiveBlocklist"]; + + // and serialize dates to string, since Sentry has a hard time with that + NSArray* dateKeys = @[@"BlockEndDate", @"LastSettingsUpdate"]; + for (NSString* dateKey in dateKeys) { + dictCopy[dateKey] = [NSDateFormatter localizedStringFromDate: dictCopy[dateKey] + dateStyle: NSDateFormatterShortStyle + timeStyle: NSDateFormatterFullStyle]; } - dispatch_source_cancel(self.syncTimer); - self.syncTimer = nil; +#ifndef TESTING + [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { + [scope setContextValue: dictCopy forKey: @"SCSettings"]; + }]; +#endif } - (void)onSettingChanged:(NSNotification*)note { @@ -504,18 +512,45 @@ - (void)onSettingChanged:(NSNotification*)note { if (!noteMoreRecentThanSettings) { NSLog(@"Ignoring setting change notification as %@ is older than %@", noteSettingUpdated, ourSettingsLastUpdated); - [self synchronizeSettings]; - return; } else { NSLog(@"Accepting propagated change (%@ --> %@) since version %d is newer than %d and/or %@ is newer than %@", note.userInfo[@"key"], note.userInfo[@"value"], noteVersionNumber, ourSettingsVersionNumber, noteSettingUpdated, ourSettingsLastUpdated); // mirror the change on our own instance - but don't propagate the change to avoid loopin [self setValue: note.userInfo[@"value"] forKey: note.userInfo[@"key"] stopPropagation: YES]; } + + // regardless of which is more recent, we should really go get the new deal from disk + // in the near future (but debounce so we don't do this a million times for rapid changes) + if (self.debouncedChangeTimer != nil) { + dispatch_source_cancel(self.debouncedChangeTimer); + self.debouncedChangeTimer = nil; + } + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + double throttleSecs = 0.25f; + self.debouncedChangeTimer = [SCMiscUtilities createDebounceDispatchTimer: throttleSecs + queue: queue + block: ^{ + NSLog(@"Syncing settings due to propagated changes"); + [self synchronizeSettings]; + }]; +} + +- (void)resetAllSettingsToDefaults { + // we _basically_ just copy the default settings dict in, + // except we leave the settings version number and last settings update + // intact - that helps keep us in sync with any other instances + NSDictionary* defaultSettings = [self defaultSettingsDict]; + for (NSString* key in defaultSettings) { + if ([key isEqualToString: @"SettingsVersionNumber"] || [key isEqualToString: @"LastSettingsUpdate"]) { + continue; + } + + [self setValue: defaultSettings[key] forKey: key]; + } } - (void)dealloc { - [self cancelSyncTimer]; + [self cancelSyncTimers]; } @synthesize settingsDict = _settingsDict, lastSynchronizedWithDisk; diff --git a/Common/SCXPCAuthorization.h b/Common/SCXPCAuthorization.h new file mode 100644 index 00000000..893836f2 --- /dev/null +++ b/Common/SCXPCAuthorization.h @@ -0,0 +1,24 @@ +// +// SCXPCAuthorization.h +// SelfControl +// +// Created by Charlie Stigler on 1/4/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCXPCAuthorization : NSObject + ++ (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command; + ++ (NSString *)authorizationRightForCommand:(SEL)command; + // For a given command selector, return the associated authorization right name. + ++ (void)setupAuthorizationRights:(AuthorizationRef)authRef; + // Set up the default authorization rights in the authorization database. + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/SCXPCAuthorization.m b/Common/SCXPCAuthorization.m new file mode 100644 index 00000000..a749a800 --- /dev/null +++ b/Common/SCXPCAuthorization.m @@ -0,0 +1,186 @@ +// +// SCXPCAuthorization.m +// SelfControl +// +// Created by Charlie Stigler on 1/4/21. +// + +#import "SCXPCAuthorization.h" + +@implementation SCXPCAuthorization + +// all of these methods (basically this whole file) copied from Apple's Even Better Authorization Sample code + +static NSString * kCommandKeyAuthRightName = @"authRightName"; +static NSString * kCommandKeyAuthRightDefault = @"authRightDefault"; +static NSString * kCommandKeyAuthRightDesc = @"authRightDescription"; + +static NSDictionary* kAuthorizationRuleAuthenticateAsAdmin2MinTimeout; + +// copied from Apple's Even Better Authorization Sample code ++ (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command + // Check that the client denoted by authData is allowed to run the specified command. + // authData is expected to be an NSData with an AuthorizationExternalForm embedded inside. +{ + #pragma unused(authData) + AuthorizationRef authRef; + + assert(command != nil); + + authRef = NULL; + + // First check that authData looks reasonable. + if ( (authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm)) ) { + return [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil]; + } + + // Create an authorization ref from that the external form data contained within. + OSStatus extFormStatus = AuthorizationCreateFromExternalForm([authData bytes], &authRef); + if (extFormStatus != errAuthorizationSuccess) { + return [NSError errorWithDomain: NSOSStatusErrorDomain code: extFormStatus userInfo: nil]; + } + + // Authorize the right associated with the command. + + AuthorizationItem oneRight = { NULL, 0, NULL, 0 }; + AuthorizationRights rights = { 1, &oneRight }; + AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed; + + oneRight.name = [[SCXPCAuthorization authorizationRightForCommand:command] UTF8String]; + assert(oneRight.name != NULL); + + OSStatus authStatus = AuthorizationCopyRights( + authRef, + &rights, + kAuthorizationEmptyEnvironment, + flags, + NULL + ); + if (authRef != NULL) { + AuthorizationFree(authRef, 0); + } + + if (authStatus != errAuthorizationSuccess) { + return [NSError errorWithDomain: NSOSStatusErrorDomain code: authStatus userInfo: nil]; + } + + return nil; +} + + ++ (NSDictionary *)commandInfo +{ + static dispatch_once_t sOnceToken; + static NSDictionary * sCommandInfo; + + // static var needs to bre defined before first use + if (kAuthorizationRuleAuthenticateAsAdmin2MinTimeout == nil) { + kAuthorizationRuleAuthenticateAsAdmin2MinTimeout = @{ + @"class": @"user", + @"group": @"admin", + @"timeout": @(120), // 2 minutes + @"shared": @(YES), + @"version": @1 // not entirely sure what this does TBH + }; + } + + dispatch_once(&sOnceToken, ^{ + #pragma clang diagnostic ignored "-Wundeclared-selector" + + + NSDictionary* startBlockCommandInfo = @{ + kCommandKeyAuthRightName : @"org.eyebeam.SelfControl.startBlock", + kCommandKeyAuthRightDefault : kAuthorizationRuleAuthenticateAsAdmin2MinTimeout, + kCommandKeyAuthRightDesc : NSLocalizedString( + @"SelfControl needs your username and password to start the block.", + @"prompt shown when user is required to authorize to start block" + ) + }; + NSDictionary* modifyBlockCommandInfo = @{ + kCommandKeyAuthRightName : @"org.eyebeam.SelfControl.modifyBlock", + kCommandKeyAuthRightDefault : kAuthorizationRuleAuthenticateAsAdmin2MinTimeout, + kCommandKeyAuthRightDesc : NSLocalizedString( + @"SelfControl needs your username and password to modify the block", + @"prompt shown when user is required to authorize to modify their block" + ) + }; + + sCommandInfo = @{ + NSStringFromSelector(@selector(startBlockWithControllingUID:blocklist:isAllowlist:endDate:blockSettings:authorization:reply:)) : startBlockCommandInfo, + NSStringFromSelector(@selector(updateBlocklist:authorization:reply:)) : modifyBlockCommandInfo, + NSStringFromSelector(@selector(updateBlockEndDate:authorization:reply:)) : modifyBlockCommandInfo + #pragma clang diagnostic pop + }; + }); + return sCommandInfo; +} + ++ (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block + // Calls the supplied block with information about each known authorization right.. +{ + [self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + #pragma unused(key) + #pragma unused(stop) + NSDictionary * commandDict; + NSString * authRightName; + id authRightDefault; + NSString * authRightDesc; + + // If any of the following asserts fire it's likely that you've got a bug + // in sCommandInfo. + + commandDict = (NSDictionary *) obj; + assert([commandDict isKindOfClass:[NSDictionary class]]); + + authRightName = [commandDict objectForKey:kCommandKeyAuthRightName]; + assert([authRightName isKindOfClass:[NSString class]]); + + authRightDefault = [commandDict objectForKey:kCommandKeyAuthRightDefault]; + assert(authRightDefault != nil); + + authRightDesc = [commandDict objectForKey:kCommandKeyAuthRightDesc]; + assert([authRightDesc isKindOfClass:[NSString class]]); + + block(authRightName, authRightDefault, authRightDesc); + }]; +} + ++ (void)setupAuthorizationRights:(AuthorizationRef)authRef + // See comment in header. +{ + assert(authRef != NULL); + [SCXPCAuthorization enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) { + OSStatus blockErr; + + // First get the right. If we get back errAuthorizationDenied that means there's + // no current definition, so we add our default one. + + blockErr = AuthorizationRightGet([authRightName UTF8String], NULL); + if (blockErr == errAuthorizationDenied) { + NSLog(@"setting auth right default for %@: %@", authRightName, authRightDefault); + blockErr = AuthorizationRightSet( + authRef, // authRef + [authRightName UTF8String], // rightName + (__bridge CFTypeRef) authRightDefault, // rightDefinition + (__bridge CFStringRef) authRightDesc, // descriptionKey + NULL, // bundle (NULL implies main bundle) + CFSTR("SCXPCAuthorization") // localeTableName + ); + assert(blockErr == errAuthorizationSuccess); + } else { + // A right already exists (err == noErr) or any other error occurs, we + // assume that it has been set up in advance by the system administrator or + // this is the second time we've run. Either way, there's nothing more for + // us to do. + } + }]; +} + ++ (NSString *)authorizationRightForCommand:(SEL)command + // See comment in header. +{ + return [self commandInfo][NSStringFromSelector(command)][kCommandKeyAuthRightName]; +} + + +@end diff --git a/Common/SCXPCClient.h b/Common/SCXPCClient.h new file mode 100644 index 00000000..dd9f06c3 --- /dev/null +++ b/Common/SCXPCClient.h @@ -0,0 +1,28 @@ +// +// SCAppXPC.h +// SelfControl +// +// Created by Charlie Stigler on 7/4/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCXPCClient : NSObject + +@property (readonly, getter=isConnected) BOOL connected; + +- (void)connectToHelperTool; +- (void)installDaemon:(void(^)(NSError*))callback; +- (void)refreshConnectionAndRun:(void(^)(void))callback; +- (void)connectAndExecuteCommandBlock:(void(^)(NSError *))commandBlock; + +- (void)getVersion:(void(^)(NSString* version, NSError* error))reply; +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings reply:(void(^)(NSError* error))reply; +- (void)updateBlocklist:(NSArray*)newBlocklist reply:(void(^)(NSError* error))reply; +- (void)updateBlockEndDate:(NSDate*)newEndDate reply:(void(^)(NSError* error))reply; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/SCXPCClient.m b/Common/SCXPCClient.m new file mode 100644 index 00000000..715de4f2 --- /dev/null +++ b/Common/SCXPCClient.m @@ -0,0 +1,359 @@ +// +// SCAppXPC.m +// SelfControl +// +// Created by Charlie Stigler on 7/4/20. +// + +#import "SCXPCClient.h" +#import "SCDaemonProtocol.h" +#import +#import "SCXPCAuthorization.h" +#import "SCErr.h" + +@interface SCXPCClient () { + AuthorizationRef _authRef; +} + +@property (atomic, strong, readwrite) NSXPCConnection* daemonConnection; +@property (atomic, copy, readwrite) NSData* authorization; + +@end + +@implementation SCXPCClient + +- (void)setupAuthorization { + // this is mostly copied from Apple's Even Better Authorization Sample + + // Create our connection to the authorization system. + // + // If we can't create an authorization reference then the app is not going to be able + // to do anything requiring authorization. Generally this only happens when you launch + // the app in some wacky, and typically unsupported, way. + + // if we've already got an authorization session, no need to make another + if (self.authorization) { + return; + } + + AuthorizationRef authRef; + OSStatus errCode = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, 0, &authRef); + if (errCode) { + NSError* err = [NSError errorWithDomain: NSOSStatusErrorDomain code: errCode userInfo: nil]; + NSLog(@"Failed to set up initial authorization with error %@", err); + [SCSentry captureError: err]; + } else { + [self updateStoredAuthorization: authRef]; + } +} + +- (void)updateStoredAuthorization:(AuthorizationRef)authRef { + self->_authRef = authRef; + if (!self->_authRef) { + self.authorization = nil; + return; + } + + AuthorizationExternalForm extForm; + OSStatus errCode = AuthorizationMakeExternalForm(self->_authRef, &extForm); + if (errCode) { + NSError* err = [NSError errorWithDomain: NSOSStatusErrorDomain code: errCode userInfo: nil]; + NSLog(@"Failed to update stored authorization with error %@", err); + [SCSentry captureError: err]; + } else { + self.authorization = [[NSData alloc] initWithBytes: &extForm length: sizeof(extForm)]; + } + + // If we successfully connected to Authorization Services, add definitions for our default + // rights (unless they're already in the database). + [SCXPCAuthorization setupAuthorizationRights: self->_authRef]; +} + +// Ensures that we're connected to our helper tool +// should only be called from the main thread +// Copied from Apple's EvenBetterAuthorizationSample +- (void)connectToHelperTool { + assert([NSThread isMainThread]); + NSLog(@"Connecting to helper tool, daemon connection is %@", self.daemonConnection); + + [self setupAuthorization]; + + if (self.daemonConnection == nil) { + self.daemonConnection = [[NSXPCConnection alloc] initWithMachServiceName: @"org.eyebeam.selfcontrold" options: NSXPCConnectionPrivileged]; + self.daemonConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SCDaemonProtocol)]; + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-retain-cycles" + // We can ignore the retain cycle warning because a) the retain taken by the + // invalidation handler block is released by us setting it to nil when the block + // actually runs, and b) the retain taken by the block passed to -addOperationWithBlock: + // will be released when that operation completes and the operation itself is deallocated + // (notably self does not have a reference to the NSBlockOperation). + // note we need a local reference to the daemonConnection since there is a race condition where + // we could reinstantiate a new connection before the handler fires, and we don't want to clear the new connection + NSXPCConnection* connection = self.daemonConnection; + connection.invalidationHandler = ^{ + // If the connection gets invalidated then, on the main thread, nil out our + // reference to it. This ensures that we attempt to rebuild it the next time around. + connection.invalidationHandler = connection.interruptionHandler = nil; + + if (connection == self.daemonConnection) { + // dispatch_sync on main thread would deadlock, so be careful + if ([NSThread isMainThread]) { + self.daemonConnection = nil; + } else { + // running this synchronously ensures that the daemonConnection is nil'd out even if + // reinstantiate the connection immediately + NSLog(@"About to dispatch_sync"); + dispatch_sync(dispatch_get_main_queue(), ^{ + self.daemonConnection = nil; + }); + } + NSLog(@"CONNECTION INVALIDATED"); + } + }; + // our interruption handler is just our invalidation handler, except we retry afterward + connection.interruptionHandler = ^{ + NSLog(@"Helper tool connection interrupted"); + connection.invalidationHandler(); + + // interruptions may have happened because the daemon crashed + // so wait a second and try to reconnect + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + NSLog(@"Retrying helper tool connection!"); + [self connectToHelperTool]; + }); + }; + + #pragma clang diagnostic pop + [self.daemonConnection resume]; + + NSLog(@"Started helper connection!"); + } +} + +- (BOOL)isConnected { + return (self.daemonConnection != nil); +} + +- (void)installDaemon:(void(^)(NSError*))callback { + AuthorizationItem blessRight = { + kSMRightBlessPrivilegedHelper, 0, NULL, 0 + }; + AuthorizationItem startBlockRight = { + "org.eyebeam.SelfControl.startBlock", 0, NULL, 0 + }; + AuthorizationItem rightsArr[] = { blessRight, startBlockRight }; + + AuthorizationRights authRights; + authRights.count = 2; + authRights.items = rightsArr; + + AuthorizationFlags myFlags = kAuthorizationFlagDefaults | + kAuthorizationFlagExtendRights | + kAuthorizationFlagInteractionAllowed; + OSStatus status; + + status = AuthorizationCopyRights( + self->_authRef, + &authRights, + kAuthorizationEmptyEnvironment, + myFlags, + NULL + ); + + if(status) { + // if it's just the user cancelling, make that obvious + // to any listeners so they can ignore it appropriately + if (status == AUTH_CANCELLED_STATUS) { + callback([SCErr errorWithCode: 1]); + } else { + NSLog(@"ERROR: Failed to authorize installing selfcontrold with status %d.", status); + + NSError* err = [SCErr errorWithCode: 501]; + [SCSentry captureError: err]; + + callback(err); + } + + return; + } + + CFErrorRef cfError; + BOOL result = (BOOL)SMJobBless( + kSMDomainSystemLaunchd, + CFSTR("org.eyebeam.selfcontrold"), + self->_authRef, + &cfError); + + if(!result) { + NSError* error = CFBridgingRelease(cfError); + + NSLog(@"WARNING: Authorized installation of selfcontrold returned failure status code %d and error %@", (int)status, error); + + NSError* err = [SCErr errorWithCode: 500 subDescription: error.localizedDescription]; + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + [SCSentry captureError: err]; + } + + callback(err); + return; + } else { + NSLog(@"Daemon installed successfully!"); + callback(nil); + } +} + +- (BOOL)connectionIsActive { + return (self.daemonConnection != nil); +} + +- (void)refreshConnectionAndRun:(void(^)(void))callback { + // when we're refreshing the connection, we can end up in a slightly awkward situation: + // if we call invalidate, but immediately start to reconnect before daemonConnection can be nil'd out + // then we risk trying to use the invalidated connection + // the fix? nil out daemonConnection before invalidating it in the refresh case + + if (self.daemonConnection == nil) { + callback(); + return; + } + void (^standardInvalidationHandler)(void) = self.daemonConnection.invalidationHandler; + + // wait until the invalidation handler runs, then run our callback + self.daemonConnection.invalidationHandler = ^{ + standardInvalidationHandler(); + callback(); + }; + + [self.daemonConnection performSelectorOnMainThread: @selector(invalidate) withObject: nil waitUntilDone: YES]; +} + +// Also copied from Apple's EvenBetterAuthorizationSample +// Connects to the helper tool and then executes the supplied command block on the +// main thread, passing it an error indicating if the connection was successful. +- (void)connectAndExecuteCommandBlock:(void(^)(NSError *))commandBlock { + // Ensure that there's a helper tool connection in place. + + [self performSelectorOnMainThread: @selector(connectToHelperTool) withObject:nil waitUntilDone: YES]; + + // Run the command block. Note that we never error in this case because, if there is + // an error connecting to the helper tool, it will be delivered to the error handler + // passed to -remoteObjectProxyWithErrorHandler:. However, I maintain the possibility + // of an error here to allow for future expansion. + + commandBlock(nil); +} + +- (void)getVersion:(void(^)(NSString* version, NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + NSLog(@"Failed to get daemon version with connection error: %@", connectError); + [SCSentry captureError: connectError]; + reply(nil, connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Failed to get daemon version with remote object proxy error: %@", proxyError); + [SCSentry captureError: proxyError]; + reply(nil, proxyError); + }] getVersionWithReply:^(NSString * _Nonnull version) { + reply(version, nil); + }]; + } + }]; +} + +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings reply:(void(^)(NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + [SCSentry captureError: connectError]; + NSLog(@"Start block command failed with connection error: %@", connectError); + reply(connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Start block command failed with remote object proxy error: %@", proxyError); + [SCSentry captureError: proxyError]; + reply(proxyError); + }] startBlockWithControllingUID: controllingUID blocklist: blocklist isAllowlist:isAllowlist endDate:endDate blockSettings: blockSettings authorization: self.authorization reply:^(NSError* error) { + if (error != nil && ![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"Start block failed with error = %@\n", error); + [SCSentry captureError: error]; + } + reply(error); + }]; + } + }]; +} + +- (void)updateBlocklist:(NSArray*)newBlocklist reply:(void(^)(NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + NSLog(@"Blocklist update failed with connection error: %@", connectError); + [SCSentry captureError: connectError]; + reply(connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Blocklist update command failed with remote object proxy error: %@", proxyError); + [SCSentry captureError: proxyError]; + reply(proxyError); + }] updateBlocklist: newBlocklist authorization: self.authorization reply:^(NSError* error) { + if (error != nil && ![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"Blocklist update failed with error = %@\n", error); + [SCSentry captureError: error]; + } + reply(error); + }]; + } + }]; +} + +- (void)updateBlockEndDate:(NSDate*)newEndDate reply:(void(^)(NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + NSLog(@"Block end date update failed with connection error: %@", connectError); + [SCSentry captureError: connectError]; + reply(connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Block end date update command failed with remote object proxy error: %@", proxyError); + [SCSentry captureError: proxyError]; + reply(proxyError); + }] updateBlockEndDate: newEndDate authorization: self.authorization reply:^(NSError* error) { + if (error != nil && ![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"Block end date update failed with error = %@\n", error); + [SCSentry captureError: error]; + } + reply(error); + }]; + } + }]; +} + +- (NSString*)selfControlHelperToolPath { + static NSString* path; + + // Cache the path so it doesn't have to be searched for again. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSBundle* thisBundle = [NSBundle mainBundle]; + path = [thisBundle.bundlePath stringByAppendingString: @"/Contents/Library/LaunchServices/org.eyebeam.selfcontrold"]; + }); + + return path; +} + +- (char*)selfControlHelperToolPathUTF8String { + static char* path; + + // Cache the converted path so it doesn't have to be converted again + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + path = malloc(512); + [[self selfControlHelperToolPath] getCString: path + maxLength: 512 + encoding: NSUTF8StringEncoding]; + }); + + return path; +} + +@end diff --git a/Common/Utility/SCBlockUtilities.h b/Common/Utility/SCBlockUtilities.h new file mode 100644 index 00000000..ec652fb1 --- /dev/null +++ b/Common/Utility/SCBlockUtilities.h @@ -0,0 +1,25 @@ +// +// SCBlockUtilities.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCBlockUtilities : NSObject + +// uses the below methods as well as filesystem checks to see if the block is REALLY running or not ++ (BOOL)anyBlockIsRunning; ++ (BOOL)modernBlockIsRunning; ++ (BOOL)legacyBlockIsRunning; + ++ (BOOL)currentBlockIsExpired; + ++ (void)removeBlockFromSettings; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/Utility/SCBlockUtilities.m b/Common/Utility/SCBlockUtilities.m new file mode 100644 index 00000000..91b8d52e --- /dev/null +++ b/Common/Utility/SCBlockUtilities.m @@ -0,0 +1,70 @@ +// +// SCBlockUtilities.m +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import "SCBlockUtilities.h" + +@implementation SCBlockUtilities + ++ (BOOL)anyBlockIsRunning { + BOOL blockIsRunning = [SCBlockUtilities modernBlockIsRunning] || [SCBlockUtilities legacyBlockIsRunning]; + + return blockIsRunning; +} + ++ (BOOL)modernBlockIsRunning { + SCSettings* settings = [SCSettings sharedSettings]; + + return [settings boolForKey: @"BlockIsRunning"]; +} + ++ (BOOL)legacyBlockIsRunning { + // first see if there's a legacy settings file from v3.x + // which could be in any user's home folder + NSError* homeDirErr = nil; + NSArray* homeDirectoryURLs = [SCMiscUtilities allUserHomeDirectoryURLs: &homeDirErr]; + if (homeDirectoryURLs != nil) { + for (NSURL* homeDirURL in homeDirectoryURLs) { + NSString* relativeSettingsPath = [NSString stringWithFormat: @"/Library/Preferences/%@", SCSettings.settingsFileName]; + NSURL* settingsFileURL = [homeDirURL URLByAppendingPathComponent: relativeSettingsPath isDirectory: NO]; + + if ([SCMigrationUtilities legacyBlockIsRunningInSettingsFile: settingsFileURL]) { + return YES; + } + } + } + + // nope? OK, how about a lock file from pre-3.0? + if ([SCMigrationUtilities legacyLockFileExists]) { + return YES; + } + + // we don't check defaults anymore, though pre-3.0 blocks did + // have data stored there. That should be covered by the lockfile anyway + + return NO; +} + +// returns YES if the block should have expired active based on the specified end time (i.e. the end time is in the past), or NO otherwise ++ (BOOL)currentBlockIsExpired { + // the block should be running if the end date hasn't arrived yet + SCSettings* settings = [SCSettings sharedSettings]; + if ([[settings valueForKey: @"BlockEndDate"] timeIntervalSinceNow] > 0) { + return NO; + } else { + return YES; + } +} + ++ (void) removeBlockFromSettings { + SCSettings* settings = [SCSettings sharedSettings]; + [settings setValue: @NO forKey: @"BlockIsRunning"]; + [settings setValue: nil forKey: @"BlockEndDate"]; + [settings setValue: nil forKey: @"ActiveBlocklist"]; + [settings setValue: nil forKey: @"ActiveBlockAsWhitelist"]; +} + +@end diff --git a/Common/Utility/SCHelperToolUtilities.h b/Common/Utility/SCHelperToolUtilities.h new file mode 100644 index 00000000..3efe4a09 --- /dev/null +++ b/Common/Utility/SCHelperToolUtilities.h @@ -0,0 +1,45 @@ +// +// SCHelperToolUtilities.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Utility methods athat are only used by the helper tools +// (i.e. selfcontrold, selfcontrol-cli, and SCKillerHelper) +// note that this is NOT included in SCUtility.h currently! +@interface SCHelperToolUtilities : NSObject + +// Reads the domain block list from the settings for SelfControl, and adds deny +// rules for all of the IPs (or the A DNS record IPS for doamin names) to the +// ipfw firewall. ++ (void)installBlockRulesFromSettings; + +// calls SMJobRemove to unload the daemon from launchd +// (which also kills the running process, synchronously) ++ (void)unloadDaemonJob; + +// Checks the settings system to see whether the user wants their web browser +// caches cleared, and deletes the specific cache folders for a few common +// web browsers if it is required. ++ (void)clearCachesIfRequested; + +// Clear only the caches for browsers ++ (NSError*)clearBrowserCaches; + +// Clear only the OS-level DNS cache ++ (void)clearOSDNSCache; + +// Removes block via settings, host file rules and ipfw rules, +// deleting user caches if requested, and migrating legacy settings. ++ (void)removeBlock; + ++ (void)sendConfigurationChangedNotification; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/Utility/SCHelperToolUtilities.m b/Common/Utility/SCHelperToolUtilities.m new file mode 100644 index 00000000..ccabb7b7 --- /dev/null +++ b/Common/Utility/SCHelperToolUtilities.m @@ -0,0 +1,181 @@ +// +// SCHelperToolUtilities.m +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import "SCHelperToolUtilities.h" +#import "BlockManager.h" +#import + +@implementation SCHelperToolUtilities + ++ (void)installBlockRulesFromSettings { + SCSettings* settings = [SCSettings sharedSettings]; + BOOL shouldEvaluateCommonSubdomains = [settings boolForKey: @"EvaluateCommonSubdomains"]; + BOOL allowLocalNetworks = [settings boolForKey: @"AllowLocalNetworks"]; + BOOL includeLinkedDomains = [settings boolForKey: @"IncludeLinkedDomains"]; + + // get value for ActiveBlockAsWhitelist + BOOL blockAsAllowlist = [settings boolForKey: @"ActiveBlockAsWhitelist"]; + + BlockManager* blockManager = [[BlockManager alloc] initAsAllowlist: blockAsAllowlist allowLocal: allowLocalNetworks includeCommonSubdomains: shouldEvaluateCommonSubdomains includeLinkedDomains: includeLinkedDomains]; + + NSLog(@"About to run BlockManager commands"); + + [blockManager prepareToAddBlock]; + [blockManager addBlockEntriesFromStrings: [settings valueForKey: @"ActiveBlocklist"]]; + [blockManager finalizeBlock]; + +} + ++ (void)unloadDaemonJob { + NSLog(@"Unloading SelfControl daemon..."); + [SCSentry addBreadcrumb: @"Daemon about to unload" category: @"daemon"]; + SCSettings* settings = [SCSettings sharedSettings]; + + // we're about to unload the launchd job + // this will kill this process, so we have to make sure + // all settings are synced before we unload + NSError* syncErr = [settings syncSettingsAndWait: 5.0]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ before unloading daemon job", syncErr); + [SCSentry captureError: syncErr]; + } + + // uh-oh, looks like it's 5 seconds later and the sync hasn't completed yet. Bad news. + CFErrorRef cfError; + // this should block until the process is dead, so we should never get to the other side if it's successful + SILENCE_OSX10_10_DEPRECATION( + SMJobRemove(kSMDomainSystemLaunchd, CFSTR("org.eyebeam.selfcontrold"), NULL, YES, &cfError); + ); + if (cfError) { + NSLog(@"Failed to remove selfcontrold daemon with error %@", cfError); + } +} + ++ (void)clearCachesIfRequested { + SCSettings* settings = [SCSettings sharedSettings]; + if(![settings boolForKey: @"ClearCaches"]) { + return; + } + + NSError* err = [SCHelperToolUtilities clearBrowserCaches]; + if (err) { + NSLog(@"WARNING: Error clearing browser caches: %@", err); + [SCSentry captureError: err]; + } + + [SCHelperToolUtilities clearOSDNSCache]; +} + ++ (NSError*)clearBrowserCaches { + NSFileManager* fileManager = [NSFileManager defaultManager]; + + NSError* homeDirErr = nil; + NSArray* homeDirectoryURLs = [SCMiscUtilities allUserHomeDirectoryURLs: &homeDirErr]; + if (homeDirectoryURLs == nil) return homeDirErr; + + NSArray* cacheDirPathComponents = @[ + // chrome + @"/Library/Caches/Google/Chrome/Default", + @"/Library/Caches/Google/Chrome/com.google.Chrome", + + // firefox + @"/Library/Caches/Firefox/Profiles", + + // safari + @"/Library/Caches/com.apple.Safari", + @"/Library/Containers/com.apple.Safari/Data/Library/Caches" // this one seems to fail due to permissions issues, but not sure how to fix + ]; + + + NSMutableArray* cacheDirURLs = [NSMutableArray arrayWithCapacity: cacheDirPathComponents.count * homeDirectoryURLs.count]; + for (NSURL* homeDirURL in homeDirectoryURLs) { + for (NSString* cacheDirPathComponent in cacheDirPathComponents) { + [cacheDirURLs addObject: [homeDirURL URLByAppendingPathComponent: cacheDirPathComponent isDirectory: YES]]; + } + } + + for (NSURL* cacheDirURL in cacheDirURLs) { + NSLog(@"Clearing browser cache folder %@", cacheDirURL); + // removeItemAtURL will return errors if the file doesn't exist + // so we don't track the errors - best effort is OK + [fileManager removeItemAtURL: cacheDirURL error: nil]; + } + + return nil; +} + ++ (void)clearOSDNSCache { + // no error checks - if it works it works! + NSTask* flushDsCacheUtil = [[NSTask alloc] init]; + [flushDsCacheUtil setLaunchPath: @"/usr/bin/dscacheutil"]; + [flushDsCacheUtil setArguments: @[@"-flushcache"]]; + [flushDsCacheUtil launch]; + [flushDsCacheUtil waitUntilExit]; + + NSTask* killResponder = [[NSTask alloc] init]; + [killResponder setLaunchPath: @"/usr/bin/killall"]; + [killResponder setArguments: @[@"-HUP", @"mDNSResponder"]]; + [killResponder launch]; + [killResponder waitUntilExit]; + + NSTask* killResponderHelper = [[NSTask alloc] init]; + [killResponderHelper setLaunchPath: @"/usr/bin/killall"]; + [killResponderHelper setArguments: @[@"mDNSResponderHelper"]]; + [killResponderHelper launch]; + [killResponderHelper waitUntilExit]; + + NSLog(@"Cleared OS DNS caches"); +} + ++ (void)playBlockEndSound { + SCSettings* settings = [SCSettings sharedSettings]; + if([settings boolForKey: @"BlockSoundShouldPlay"]) { + // Map the tags used in interface builder to the sound + NSArray* systemSoundNames = SCConstants.systemSoundNames; + NSSound* alertSound = [NSSound soundNamed: systemSoundNames[[[settings valueForKey: @"BlockSound"] intValue]]]; + if(!alertSound) + NSLog(@"WARNING: Alert sound not found."); + else { + [alertSound play]; + } + } +} + ++ (void)removeBlock { + [SCBlockUtilities removeBlockFromSettings]; + [[BlockManager new] clearBlock]; + + [SCHelperToolUtilities clearCachesIfRequested]; + + // play a sound letting + [SCHelperToolUtilities playBlockEndSound]; + + // always synchronize settings ASAP after removing a block to let everybody else know + // and wait until they're synced before we send the configuration change notification + // so the app has no chance of reading the data before we update it + NSError* syncErr = [[SCSettings sharedSettings] syncSettingsAndWait: 5.0]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after removing block", syncErr); + [SCSentry captureError: syncErr]; + } + + // let the main app know things have changed so it can update the UI! + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + NSLog(@"INFO: Block cleared."); +} + ++ (void)sendConfigurationChangedNotification { + // if you don't include the NSNotificationPostToAllSessions option, + // it will not deliver when run by launchd (root) to the main app being run by the user + [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification" + object: nil + userInfo: nil + options: NSNotificationDeliverImmediately | NSNotificationPostToAllSessions]; +} + +@end diff --git a/Common/Utility/SCMigrationUtilities.h b/Common/Utility/SCMigrationUtilities.h new file mode 100644 index 00000000..44ee95d6 --- /dev/null +++ b/Common/Utility/SCMigrationUtilities.h @@ -0,0 +1,34 @@ +//SCMigrationUtilities// SCMigration.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Utility methods dealing with legacy settings, legacy blocks, +// and migrating us from old versions of the app to the new one + +@interface SCMigrationUtilities : NSObject + ++ (NSString*)legacySecuredSettingsFilePathForUser:(uid_t)userId; + ++ (BOOL)legacySettingsFoundForUser:(uid_t)controllingUID; ++ (BOOL)legacySettingsFoundForCurrentUser; ++ (BOOL)legacyLockFileExists; + ++ (BOOL)legacyBlockIsRunningInSettingsFile:(NSURL*)settingsFileURL; ++ (BOOL)blockIsRunningInLegacyDictionary:(NSDictionary*)dict; + ++ (NSDate*)legacyBlockEndDate; + ++ (void)copyLegacySettingsToDefaults:(uid_t)controllingUID; ++ (void)copyLegacySettingsToDefaults; + ++ (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/Utility/SCMigrationUtilities.m b/Common/Utility/SCMigrationUtilities.m new file mode 100644 index 00000000..756d12bd --- /dev/null +++ b/Common/Utility/SCMigrationUtilities.m @@ -0,0 +1,289 @@ +// +// SCMigrationUtilities.m +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#define SelfControlLegacyLockFilePath @"/etc/SelfControl.lock" + +#import "SCMigrationUtilities.h" +#import +#import "SCSettings.h" + +@implementation SCMigrationUtilities + ++ (NSString*)homeDirectoryForUid:(uid_t)uid { + struct passwd *pwd = getpwuid(uid); + return [NSString stringWithCString: pwd->pw_dir encoding: NSString.defaultCStringEncoding]; +} + ++ (NSString*)legacySecuredSettingsFilePathForUser:(uid_t)userId { + NSString* homeDir = [SCMigrationUtilities homeDirectoryForUid: userId]; + return [[NSString stringWithFormat: @"%@/Library/Preferences/%@", homeDir, SCSettings.settingsFileName] stringByExpandingTildeInPath]; +} + +// check all legacy settings (old secured settings, lockfile, old-school defaults) +// to see if there's anything there ++ (BOOL)legacySettingsFoundForUser:(uid_t)controllingUID { + NSFileManager* fileMan = [NSFileManager defaultManager]; + NSString* legacySettingsPath = [SCMigrationUtilities legacySecuredSettingsFilePathForUser: controllingUID]; + NSArray* defaultsHostBlacklist; + + if (geteuid() == 0 && controllingUID) { + // we're running as root, so get the defaults dictionary using our special function) + NSDictionary* defaultsDict = [SCMiscUtilities defaultsDictForUser: controllingUID]; + defaultsHostBlacklist = defaultsDict[@"HostBlacklist"]; + } else { + // normal times, just use standard defaults + defaultsHostBlacklist = [[NSUserDefaults standardUserDefaults] objectForKey: @"HostBlacklist"]; + } + + return defaultsHostBlacklist || [fileMan fileExistsAtPath: legacySettingsPath] || [fileMan fileExistsAtPath: SelfControlLegacyLockFilePath]; +} ++ (BOOL)legacySettingsFoundForCurrentUser { + return [SCMigrationUtilities legacySettingsFoundForUser: getuid()]; +} + ++ (BOOL)legacyLockFileExists { + return [[NSFileManager defaultManager] fileExistsAtPath: SelfControlLegacyLockFilePath]; +} + ++ (NSDate*)legacyBlockEndDate { + // if we're running this as a normal user (generally that means app/CLI), it's easy: just get the standard user defaults + // this method can't be run as root, it won't work + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary* lockDict = [NSDictionary dictionaryWithContentsOfFile: SelfControlLegacyLockFilePath]; + NSString* legacySettingsPath = [SCMigrationUtilities legacySecuredSettingsFilePathForUser: getuid()]; + NSDictionary* settingsFromDisk = [NSDictionary dictionaryWithContentsOfFile: legacySettingsPath]; + + // if we have a v3.x settings dictionary, take from that + if (settingsFromDisk != nil && settingsFromDisk[@"BlockEndDate"] != nil) { + return settingsFromDisk[@"BlockEndDate"]; + } + + // otherwise, we can look in defaults or the lockfile, both from pre-3.x versions + // these would have BlockStartedDate + BlockDuration instead of BlockEndDate, so conversion is needed + NSDate* startDate = [defaults objectForKey: @"BlockStartedDate"]; + NSTimeInterval duration = [defaults floatForKey: @"BlockDuration"]; + + // if defaults didn't have valid values, try the lockfile + if (startDate == nil || [startDate timeIntervalSinceNow] >= 0 || duration <= 0) { + startDate = lockDict[@"BlockStartedDate"]; + duration = [lockDict[@"BlockStartedDate"] floatValue]; + } + if (startDate == nil || [startDate timeIntervalSinceNow] >= 0 || duration <= 0) { + // if still not, we give up! no end date found, so call it the past + return [NSDate distantPast]; + } + return [startDate dateByAddingTimeInterval: (duration * 60)]; +} + ++ (BOOL)legacyBlockIsRunningInSettingsFile:(NSURL*)settingsFileURL { + NSDictionary* legacySettingsDict = [NSDictionary dictionaryWithContentsOfURL: settingsFileURL]; + + // if the file doesn't exist, there's definitely no block + if (legacySettingsDict == nil) return NO; + + return [SCMigrationUtilities blockIsRunningInLegacyDictionary: legacySettingsDict]; +} + ++ (BOOL)blockIsRunningInLegacyDictionary:(NSDictionary*)dict { + if (dict == nil) return NO; + + NSDate* blockStartedDate = [dict objectForKey:@"BlockStartedDate"]; + BOOL blockIsRunningValue = [[dict objectForKey: @"BlockIsRunning"] boolValue]; + + // for v3.0-3.0.3: the block is running if the BlockIsRunning key is true + // super old legacy (pre-3.0): the block is running if BlockStartedDate exists and isn't equal to the default value + if (blockIsRunningValue || (blockStartedDate != nil && ![blockStartedDate isEqualToDate: [NSDate distantFuture]])) { + return YES; + } else { + return NO; + } +} + +// copies settings from legacy locations (user-based secured settings used from 3.0-3.0.3, +// or older defaults/lockfile used pre-3.0) to their modern destinations in NSUserDefaults. +// does NOT update any of the values in SCSettings, and does NOT clear out settings from anywhere +// that makes this safe to call anytime, includig while a block is running ++ (void)copyLegacySettingsToDefaults:(uid_t)controllingUID { + NSLog(@"Copying legacy settings to defaults..."); + BOOL runningAsRoot = (geteuid() == 0); + if (runningAsRoot && !controllingUID) { + // if we're running as root, but we didn't get a valid non-root controlling UID + // we don't really have anywhere to copy those legacy settings to, because root doesn't have defaults + NSLog(@"WARNING: Can't copy legacy settings to defaults, because SCSettings is being run as root and no controlling UID was sent."); + return; + } + if (!controllingUID) controllingUID = getuid(); + + NSDictionary* defaultDefaults = SCConstants.defaultUserDefaults; + // if we're running this as a normal user (generally that means app/CLI), it's easy: just get the standard user defaults + // if we're running this as root, we need to be given a UID target, then we imitate them to grab their defaults + NSUserDefaults* defaults; + if (runningAsRoot) { + seteuid(controllingUID); + defaults = [NSUserDefaults standardUserDefaults]; + [defaults addSuiteNamed: @"org.eyebeam.SelfControl"]; + [defaults registerDefaults: SCConstants.defaultUserDefaults]; + [defaults synchronize]; + } else { + defaults = [NSUserDefaults standardUserDefaults]; + } + + // if we already completed a migration into these defaults, DON'T do it again! + // (we don't want to overwrite any changes post-migration) + BOOL migrationComplete = [defaults boolForKey: @"V4MigrationComplete"]; + + if (!migrationComplete) { + NSDictionary* lockDict = [NSDictionary dictionaryWithContentsOfFile: SelfControlLegacyLockFilePath]; + + NSString* legacySettingsPath = [SCMigrationUtilities legacySecuredSettingsFilePathForUser: controllingUID]; + NSDictionary* settingsFromDisk = [NSDictionary dictionaryWithContentsOfFile: legacySettingsPath]; + + // if we have a v3.x settings dictionary, copy what we can from that + if (settingsFromDisk != nil) { + NSLog(@"Migrating all settings from legacy secured settings file %@", legacySettingsPath); + + // we assume the settings from disk are newer / should override existing values + // UNLESS the user has set a default to its non-default value + + // we'll look at all the possible keys in defaults - some of them should really + // have never ended up in settings at any point, but shouldn't matter + for (NSString* key in [defaultDefaults allKeys]) { + id settingsValue = settingsFromDisk[key]; + id defaultsValue = [defaults objectForKey: key]; + + // we have a value from settings, and the defaults value is unset or equal to the default value + // so pull the value from settings in! + if (settingsValue != nil && (defaultsValue == nil || [defaultsValue isEqualTo: defaultDefaults[key]])) { + NSLog(@"Migrating keypair (%@, %@) from settings to defaults", key, settingsValue); + [defaults setObject: settingsValue forKey: key]; + } + } + + NSLog(@"Done migrating preferences from legacy secured settings to defaults!"); + } + + // if we're on a pre-3.0 version, we may need to migrate the blocklist from defaults or the lock dictionary + // the Blocklist attribute used to be named HostBlacklist, so needs a special migration + NSArray* blocklistInDefaults = [defaults arrayForKey: @"Blocklist"]; + // of course, don't overwrite if we already have a blocklist in today's defaults + if (blocklistInDefaults == nil || blocklistInDefaults.count == 0) { + if (lockDict != nil && lockDict[@"HostBlacklist"] != nil) { + [defaults setObject: lockDict[@"HostBlacklist"] forKey: @"Blocklist"]; + NSLog(@"Migrated blocklist from pre-3.0 lock dictionary: %@", lockDict[@"HostBlacklist"]); + } else if ([defaults objectForKey: @"HostBlacklist"] != nil) { + [defaults setObject: [defaults objectForKey: @"HostBlacklist"] forKey: @"Blocklist"]; + NSLog(@"Migrated blocklist from pre-3.0 legacy defaults: %@", [defaults objectForKey: @"HostBlacklist"]); + } + } + + [defaults setBool: YES forKey: @"V4MigrationComplete"]; + } else { + NSLog(@"Skipping copy to defaults because migration to V4 was already completed."); + } + + [defaults synchronize]; + // if we're running as root and imitated the user to get their defaults, we need to put things back in place when done + if (runningAsRoot) { + [NSUserDefaults resetStandardUserDefaults]; + seteuid(0); + } + + [SCSentry addBreadcrumb: @"Copied legacy settings to defaults successfully" category: @"settings"]; + NSLog(@"Done copying settings!"); +} + ++ (void)copyLegacySettingsToDefaults { + [SCMigrationUtilities copyLegacySettingsToDefaults: 0]; +} + +// We might have "legacy" block settings hiding in one of three places: +// - a "lock file" at /etc/SelfControl.lock (aka SelfControlLegacyLockFilePath) +// - the defaults system +// - a v3.x per-user secured settings file +// we should check for block settings in all of these places and get rid of them ++ (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID { + NSLog(@"Clearing legacy settings!"); + + BOOL runningAsRoot = (geteuid() == 0); + if (!runningAsRoot || !controllingUID) { + // if we're not running as root, or we didn't get a valid non-root controlling UID + // we won't have permissions to make this work. This method MUST be called with root perms + NSLog(@"ERROR: Can't clear legacy settings, because we aren't running as root."); + NSError* err = [SCErr errorWithCode: 701]; + [SCSentry captureError: err]; + return err; + } + + // if we're gonna clear settings, there can't be a block running anywhere. otherwise, we should wait! + if ([SCBlockUtilities legacyBlockIsRunning]) { + NSLog(@"ERROR: Can't clear legacy settings because a block is ongoing!"); + NSError* err = [SCErr errorWithCode: 702]; + [SCSentry captureError: err]; + return err; + } + + NSFileManager* fileMan = [NSFileManager defaultManager]; + + // besides Blocklist and the values copied from the v3.0-3.0.3 settings file to defaults in copyLegacySettingsToDefaults + // we actually don't need to move anything else over! Why? + // 1. The other settings from 3.0-3.0.3 don't matter as long as a block isn't running (i.e. BlockIsRunning should be false + // and BlockEndDate shouldn't be set). + // 2. All of the non-block settings from pre-3.0 can stay in defaults, ahd BlockStartedDate should be false if no block running + // so all that's left is to clear out the legacy crap for good + + // if an error happens trying to clear any portion of the old settings, + // we'll remember it, log it, and return it, but still try to clear the rest (best-effort) + NSError* retErr = nil; + + // first, clear the pre-3.0 lock dictionary + if(![fileMan removeItemAtPath: SelfControlLegacyLockFilePath error: &retErr] && [fileMan fileExistsAtPath: SelfControlLegacyLockFilePath]) { + NSLog(@"WARNING: Could not remove legacy SelfControl lock file because of error: %@", retErr); + [SCSentry captureError: retErr]; + } + + // then, clear keys out of defaults which aren't used + // prepare defaults by imitating the appropriate user + seteuid(controllingUID); + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults addSuiteNamed: @"org.eyebeam.SelfControl"]; + [defaults registerDefaults: SCConstants.defaultUserDefaults]; + [defaults synchronize]; + NSArray* defaultsKeysToClear = @[ + @"BlockStartedDate", + @"BlockEndDate", + @"HostBlacklist" + ]; + for (NSString* key in defaultsKeysToClear) { + [defaults removeObjectForKey: key]; + } + [defaults synchronize]; + [NSUserDefaults resetStandardUserDefaults]; + seteuid(0); + + // clear all legacy per-user secured settings (v3.0-3.0.3) in every user's home folder + NSArray* homeDirectoryURLs = [SCMiscUtilities allUserHomeDirectoryURLs: &retErr]; + if (homeDirectoryURLs != nil) { + for (NSURL* homeDirURL in homeDirectoryURLs) { + NSString* relativeSettingsPath = [NSString stringWithFormat: @"/Library/Preferences/%@", SCSettings.settingsFileName]; + NSURL* settingsFileURL = [homeDirURL URLByAppendingPathComponent: relativeSettingsPath isDirectory: NO]; + + if(![fileMan removeItemAtURL: settingsFileURL error: &retErr] && [fileMan fileExistsAtPath: settingsFileURL.path]) { + NSLog(@"WARNING: Could not remove legacy SelfControl settings file at URL %@ because of error: %@", settingsFileURL, retErr); + [SCSentry captureError: retErr]; + } + } + } + + // and that's it! note that we don't touch the modern SCSettings at all, and that's OK - it'll restart from scratch and be fine + [SCSentry addBreadcrumb: @"Cleared legacy settings successfully" category: @"settings"]; + NSLog(@"Cleared legacy settings!"); + + return retErr; +} + + +@end diff --git a/Common/Utility/SCMiscUtilities.h b/Common/Utility/SCMiscUtilities.h new file mode 100644 index 00000000..ee9c8623 --- /dev/null +++ b/Common/Utility/SCMiscUtilities.h @@ -0,0 +1,30 @@ +// +// SCMiscUtilities.h +// SelfControl +// +// Created by Charles Stigler on 07/07/2018. +// + +#import +#import "SCMigrationUtilities.h" + +// Holds utility methods for use throughout SelfControl + + +@interface SCMiscUtilities : NSObject + ++ (dispatch_source_t)createDebounceDispatchTimer:(double) debounceTime queue:(dispatch_queue_t)queue block:(dispatch_block_t)block; + ++ (NSString *)getSerialNumber; ++ (NSString *)sha1:(NSString*)stringToHash; + ++ (NSArray*) cleanBlocklistEntry:(NSString*)rawEntry; + ++ (NSDictionary*) defaultsDictForUser:(uid_t)controllingUID; + ++ (NSArray*)allUserHomeDirectoryURLs:(NSError**)errPtr; + ++ (BOOL)errorIsAuthCanceled:(NSError*)err; + + +@end diff --git a/Common/Utility/SCMiscUtilities.m b/Common/Utility/SCMiscUtilities.m new file mode 100644 index 00000000..4a5d9645 --- /dev/null +++ b/Common/Utility/SCMiscUtilities.m @@ -0,0 +1,223 @@ +// +// SCMiscUtilities.m +// SelfControl +// +// Created by Charles Stigler on 07/07/2018. +// + +#import "SCHelperToolUtilities.h" +#import "SCSettings.h" +#import +#include + +@implementation SCMiscUtilities + +// copied from stevenojo's GitHub snippet: https://gist.github.com/stevenojo/e1dcc2b3e2fd4ed1f411eef88e254cb0 ++ (dispatch_source_t)createDebounceDispatchTimer:(double)debounceTime queue:(dispatch_queue_t)queue block:(dispatch_block_t)block { + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + + if (timer) { + dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, debounceTime * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, (1ull * NSEC_PER_SEC) / 10); + dispatch_source_set_event_handler(timer, block); + dispatch_resume(timer); + } + + return timer; +} + +// by Martin R et al on StackOverflow: https://stackoverflow.com/a/15451318 ++ (NSString *)getSerialNumber { + NSString *serial = nil; + io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching("IOPlatformExpertDevice")); + if (platformExpert) { + CFTypeRef serialNumberAsCFString = + IORegistryEntryCreateCFProperty(platformExpert, + CFSTR(kIOPlatformSerialNumberKey), + kCFAllocatorDefault, 0); + if (serialNumberAsCFString) { + serial = CFBridgingRelease(serialNumberAsCFString); + } + + IOObjectRelease(platformExpert); + } + return serial; +} +// by hypercrypt et al on StackOverflow: https://stackoverflow.com/a/7571583 ++ (NSString *)sha1:(NSString*)stringToHash +{ + NSData *data = [stringToHash dataUsingEncoding:NSUTF8StringEncoding]; + uint8_t digest[CC_SHA1_DIGEST_LENGTH]; + + CC_SHA1(data.bytes, (CC_LONG)data.length, digest); + + NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; + + for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) + { + [output appendFormat:@"%02x", digest[i]]; + } + + return output; +} + +// Standardize and clean up the input value so it'll block properly (and look good doing it) +// note that if the user entered line breaks, we'll split it into many entries, so this can return multiple +// cleaned entries in the NSArray it returns ++ (NSArray*) cleanBlocklistEntry:(NSString*)rawEntry { + if (rawEntry == nil) return @[]; + + // This'll remove whitespace and lowercase the string. + NSString* str = [[rawEntry stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString]; + + // if there are newlines in the string, split it and process it as many strings + if([str rangeOfCharacterFromSet: [NSCharacterSet newlineCharacterSet]].location != NSNotFound) { + NSArray* splitEntries = [str componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]; + + NSMutableArray* returnArr = [NSMutableArray new]; + for (NSString* splitEntry in splitEntries) { + // recursion makes the rest of the code prettier + NSArray* cleanedSubEntries = [SCMiscUtilities cleanBlocklistEntry: splitEntry]; + [returnArr addObjectsFromArray: cleanedSubEntries]; + } + return returnArr; + } + + // if the user entered a scheme (https://, http://, etc) remove it. + // We only block hostnames so scheme is ignored anyway and it can gunk up the blocking + NSArray* separatedStr = [str componentsSeparatedByString: @"://"]; + str = [separatedStr lastObject]; + + // Remove URL login names/passwords (username:password@host) if a user tried to put that in + separatedStr = [str componentsSeparatedByString: @"@"]; + str = [separatedStr lastObject]; + + // now here's where it gets tricky. Besides just hostnames, we also support CIDR IP ranges, for example: 83.0.1.2/24 + // so we are gonna keep track of whether we might have received a valid CIDR IP range instead of hostname as we go... + // we also take port numbers, so keep track of whether we have one of those + int cidrMaskBits = -1; + int portNum = -1; + + // first pull off everything after a slash + // discard the end if it's just a path, but check to see if it might be our CIDR mask length + separatedStr = [str componentsSeparatedByString: @"/"]; + str = [separatedStr firstObject]; + + // if the part after a slash is an integer between 1 and 128, it could be our mask length + if (separatedStr.count > 1) { + int potentialMaskLen = [[separatedStr lastObject] intValue]; + if (potentialMaskLen > 0 && potentialMaskLen <= 128) cidrMaskBits = potentialMaskLen; + } + + // check for the port + separatedStr = [str componentsSeparatedByString: @":"]; + str = [separatedStr firstObject]; + + if (separatedStr.count > 1) { + int potentialPort = [[separatedStr lastObject] intValue]; + if (potentialPort > 0 && potentialPort <= 65535) { + portNum = potentialPort; + } + } + + // remove invalid characters from the hostname + // hostnames are 1-253 characters long, and can contain only a-z, A-Z, 0-9, -, and ., and maybe _ (mostly not but kinda) + // for some reason [NSCharacterSet URLHostAllowedCharacterSet] has tons of other characters that aren't actually valid + NSMutableCharacterSet* invalidHostnameChars = [[NSCharacterSet alphanumericCharacterSet] mutableCopy]; + [invalidHostnameChars addCharactersInString: @"-._"]; + [invalidHostnameChars invert]; + + NSMutableString* validCharsOnly = [NSMutableString stringWithCapacity: str.length]; + for (NSUInteger i = 0; i < str.length && i < 253; i++) { + unichar c = [str characterAtIndex: i]; + if (![invalidHostnameChars characterIsMember: c]) { + [validCharsOnly appendFormat: @"%C", c]; + } + } + str = validCharsOnly; + + // allow blocking an empty hostname IFF we're only blocking a single port number (i.e. :80) + // otherwise, empty hostname = nothing to do + if (str.length < 1 && portNum < 0) { + return @[]; + } + + NSString* maskString; + NSString* portString; + + // create a mask string if we have one + if (cidrMaskBits < 0) { + maskString = @""; + } else { + maskString = [NSString stringWithFormat: @"/%d", cidrMaskBits]; + } + + // create a port string if we have one + if (portNum < 0) { + portString = @""; + } else { + portString = [NSString stringWithFormat: @":%d", portNum]; + } + + // combine em together and you got something! + return @[[NSString stringWithFormat: @"%@%@%@", str, maskString, portString]]; +} + ++ (NSDictionary*) defaultsDictForUser:(uid_t) controllingUID { + if (geteuid() != 0) { + // if we're not root, we can't just get defaults for some arbitrary user + return nil; + } + + // pull up the user's defaults in the old legacy way + // to do that, we have to seteuid to the controlling UID so NSUserDefaults thinks we're them + seteuid(controllingUID); + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults addSuiteNamed: @"org.eyebeam.SelfControl"]; + [defaults registerDefaults: SCConstants.defaultUserDefaults]; + [defaults synchronize]; + NSDictionary* dictValue = [defaults dictionaryRepresentation]; + // reset the euid so nothing else gets funky + [NSUserDefaults resetStandardUserDefaults]; + seteuid(0); + + return dictValue; +} + ++ (BOOL)errorIsAuthCanceled:(NSError*)err { + if (err == nil) return NO; + + if ([err.domain isEqualToString: NSOSStatusErrorDomain] && err.code == AUTH_CANCELLED_STATUS) { + return YES; + } + if (err.domain == kSelfControlErrorDomain && err.code == 1) { + return YES; + } + + return NO; +} + ++ (NSArray*)allUserHomeDirectoryURLs:(NSError**)errPtr { + NSError* retErr = nil; + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSURL* usersFolderURL = [NSURL fileURLWithPath: @"/Users"]; + NSArray* homeDirectoryURLs = [fileManager contentsOfDirectoryAtURL: usersFolderURL + includingPropertiesForKeys: @[NSURLPathKey, NSURLIsDirectoryKey, NSURLIsReadableKey] + options: NSDirectoryEnumerationSkipsHiddenFiles + error: &retErr]; + if (homeDirectoryURLs == nil || homeDirectoryURLs.count == 0) { + if (retErr != nil) { + *errPtr = retErr; + } else { + *errPtr = [SCErr errorWithCode: 700]; + } + + [SCSentry captureError: *errPtr]; + + return nil; + } + + return homeDirectoryURLs; +} + +@end diff --git a/Common/Utility/SCUtility.h b/Common/Utility/SCUtility.h new file mode 100644 index 00000000..d30bcf4a --- /dev/null +++ b/Common/Utility/SCUtility.h @@ -0,0 +1,16 @@ +// +// SCUtility.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#ifndef SCUtility_h +#define SCUtility_h + +#import "SCMigrationUtilities.h" +#import "SCBlockUtilities.h" +#import "SCMiscUtilities.h" +#import "SCHelperToolUtilities.h" + +#endif /* SCUtility_h */ diff --git a/Credits.rtf b/Credits.rtf index 44539ee3..f786a345 100755 --- a/Credits.rtf +++ b/Credits.rtf @@ -1,14 +1,15 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1348\cocoasubrtf170 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\rtf1\ansi\ansicpg1252\cocoartf2578 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} \vieww9000\viewh8400\viewkind0 -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0 {\field{\*\fldinst{HYPERLINK "http://selfcontrolapp.com"}}{\fldrslt \f0\fs22 \cf0 http://selfcontrolapp.com}} \f0\fs22 \ \ Developed by {\field{\*\fldinst{HYPERLINK "http://charliestigler.com"}}{\fldrslt Charlie Stigler}}, {\field{\*\fldinst{HYPERLINK "http://visitsteve.com/"}}{\fldrslt Steve Lambert}}, and you?\ \ -Icon by {\field{\*\fldinst{HYPERLINK "https://github.com/josephfusco"}}{\fldrslt Joseph Fusco}}, contributions by all of {\field{\*\fldinst{HYPERLINK "https://github.com/SelfControlApp/selfcontrol/graphs/contributors"}}{\fldrslt these lovely folks}}, and translations by {\field{\*\fldinst{HYPERLINK "https://github.com/SelfControlApp/selfcontrol/wiki/Translation-Credits"}}{\fldrslt these troopers}}.\ +Icon by {\field{\*\fldinst{HYPERLINK "https://github.com/josephfusco"}}{\fldrslt Joseph Fusco}}, contributions by all of {\field{\*\fldinst{HYPERLINK "https://github.com/SelfControlApp/selfcontrol/graphs/contributors"}}{\fldrslt these lovely folks}}, and translations by {\field{\*\fldinst{HYPERLINK "https://github.com/SelfControlApp/selfcontrol/wiki/Translation-Credits"}}{\fldrslt these troopers}}. Error reporting generously provided by {\field{\*\fldinst{HYPERLINK "https://sentry.io/"}}{\fldrslt Sentry}}.\ \ Free Software created at {\field{\*\fldinst{HYPERLINK "http://eyebeam.org/"}}{\fldrslt Eyebeam}} under the {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/copyleft/gpl.html"}}{\fldrslt GNU GPL}}. {\field{\*\fldinst{HYPERLINK "https://github.com/slambert/selfcontrol/"}}{\fldrslt Source code}} is available on GitHub.} \ No newline at end of file diff --git a/Daemon/DaemonMain.m b/Daemon/DaemonMain.m new file mode 100644 index 00000000..43a89ff3 --- /dev/null +++ b/Daemon/DaemonMain.m @@ -0,0 +1,25 @@ +// +// DaemonMain.m +// SelfControl +// +// Created by Charlie Stigler on 5/28/20. +// + +#import +#import "SCDaemon.h" + +// Entry point for the SelfControl daemon process (selfcontrold) +int main(int argc, const char *argv[]) { + [SCSentry startSentry: @"org.eyebeam.selfcontrold"]; + + // get the daemon object going + SCDaemon* daemon = [SCDaemon sharedDaemon]; + [daemon start]; + + NSLog(@"running forever"); + + // never gonna give you up, never gonna let you down, never gonna run around and desert you... + [[NSRunLoop currentRunLoop] run]; + + return 0; +} diff --git a/Daemon/SCDaemon.h b/Daemon/SCDaemon.h new file mode 100644 index 00000000..1e857311 --- /dev/null +++ b/Daemon/SCDaemon.h @@ -0,0 +1,40 @@ +// +// SCDaemon.h +// SelfControl +// +// Created by Charlie Stigler on 5/28/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// SCDaemon is the top-level class that runs the SelfControl +// daemon process (selfcontrold). It runs from DaemonMain. +@interface SCDaemon : NSObject + +// Singleton instance of SCDaemon ++ (instancetype)sharedDaemon; + + +// Starts the daemon tasks, including accepting XPC connections +// and running block checkup jobs if necessary +- (void)start; + +// Starts checking up on the block on a regular basis +// to make sure it hasn't expired, been tampered with, etc +// (and will remove it or fix it if so) +- (void)startCheckupTimer; + +// Stops the checkup timer (this should only be called if there's +// no block running, because we should have checkups going for all blocks) +- (void)stopCheckupTimer; + +// Lets the daemon know that there was recent activity +// so we can reset our inactivity timer. +// The daemon will die if goes for too long without activity. +- (void)resetInactivityTimer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Daemon/SCDaemon.m b/Daemon/SCDaemon.m new file mode 100644 index 00000000..cdc639a0 --- /dev/null +++ b/Daemon/SCDaemon.m @@ -0,0 +1,176 @@ +// +// SCDaemon.m +// SelfControl +// +// Created by Charlie Stigler on 5/28/20. +// + +#import "SCDaemon.h" +#import "SCDaemonProtocol.h" +#import "SCDaemonXPC.h" +#import"SCDaemonBlockMethods.h" +#import "HostFileBlocker.h" + +static NSString* serviceName = @"org.eyebeam.selfcontrold"; +float const INACTIVITY_LIMIT_SECS = 60 * 2; // 2 minutes + +@interface NSXPCConnection(PrivateAuditToken) + +// This property exists, but it's private. Make it available: +@property (nonatomic, readonly) audit_token_t auditToken; + +@end + +@interface SCDaemon () + +@property (nonatomic, strong, readwrite) NSXPCListener* listener; +@property (strong, readwrite) NSTimer* checkupTimer; +@property (strong, readwrite) NSTimer* inactivityTimer; +@property (nonatomic, strong, readwrite) NSDate* lastActivityDate; + +@end + +@implementation SCDaemon + ++ (instancetype)sharedDaemon { + static SCDaemon* daemon = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + daemon = [SCDaemon new]; + }); + return daemon; +} + +- (id) init { + _listener = [[NSXPCListener alloc] initWithMachServiceName: serviceName]; + _listener.delegate = self; + + return self; +} + +- (void)start { + [self.listener resume]; + + // if there's any evidence of a block (i.e. an official one running, + // OR just block remnants remaining in hosts), we should start + // running checkup regularly so the block gets found/removed + // at the proper time. + // we do NOT run checkup if there's no block, because it can result + // in the daemon actually unloading itself before the app has a chance + // to start the block + if ([SCBlockUtilities anyBlockIsRunning] || [HostFileBlocker blockFoundInHostsFile]) { + [self startCheckupTimer]; + } + + [self startInactivityTimer]; + [self resetInactivityTimer]; +} + +- (void)startCheckupTimer { + // this method must always be called on the main thread, so the timer will work properly + if (![NSThread isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self startCheckupTimer]; + }); + return; + } + + // if the timer's already running, don't stress it! + if (self.checkupTimer != nil) { + return; + } + + self.checkupTimer = [NSTimer scheduledTimerWithTimeInterval: 1 repeats: YES block:^(NSTimer * _Nonnull timer) { + [SCDaemonBlockMethods checkupBlock]; + }]; + + // run the first checkup immediately! + [SCDaemonBlockMethods checkupBlock]; +} +- (void)stopCheckupTimer { + if (self.checkupTimer == nil) { + return; + } + + [self.checkupTimer invalidate]; + self.checkupTimer = nil; +} + + +- (void)startInactivityTimer { + self.inactivityTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 repeats: YES block:^(NSTimer * _Nonnull timer) { + // we haven't had any activity in a while, the daemon appears to be idling + // so kill it to avoid the user having unnecessary processes running! + if ([[NSDate date] timeIntervalSinceDate: self.lastActivityDate] > INACTIVITY_LIMIT_SECS) { + // if we're inactive but also there's a block running, that's a bad thing + // start the checkups going again - unclear why they would've stopped + if ([SCBlockUtilities anyBlockIsRunning] || [HostFileBlocker blockFoundInHostsFile]) { + [self startCheckupTimer]; + [SCDaemonBlockMethods checkupBlock]; + return; + } + + NSLog(@"Daemon inactive for more than %f seconds, exiting!", INACTIVITY_LIMIT_SECS); + [SCHelperToolUtilities unloadDaemonJob]; + } + }]; +} +- (void)resetInactivityTimer { + self.lastActivityDate = [NSDate date]; +} + +- (void)dealloc { + if (self.checkupTimer) { + [self.checkupTimer invalidate]; + self.checkupTimer = nil; + } + if (self.inactivityTimer) { + [self.inactivityTimer invalidate]; + self.inactivityTimer = nil; + } +} + +#pragma mark - NSXPCListenerDelegate + +- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { + // There is a potential security issue / race condition with matching based on PID, so we use the (technically private) auditToken instead + audit_token_t auditToken = newConnection.auditToken; + NSDictionary* guestAttributes = @{ + (id)kSecGuestAttributeAudit: [NSData dataWithBytes: &auditToken length: sizeof(audit_token_t)] + }; + SecCodeRef guest; + if (SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef _Nullable)(guestAttributes), kSecCSDefaultFlags, &guest) != errSecSuccess) { + return NO; + } + + SecRequirementRef isSelfControlApp; + // versions before 4.0 didn't have hardened code signing, so aren't trustworthy to talk to the daemon + // (plus the daemon didn't exist before 4.0 so there's really no reason they should want to run it!) + // TODO: does this work using build number? are old versions really unable to connect? + // TODO: move this to the final 4.0 build number + SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\") and info [CFBundleVersion] >= \"399\" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = L6W5L88KN7)"), kSecCSDefaultFlags, &isSelfControlApp); + OSStatus clientValidityStatus = SecCodeCheckValidity(guest, kSecCSDefaultFlags, isSelfControlApp); + + CFRelease(guest); + CFRelease(isSelfControlApp); + + if (clientValidityStatus) { + NSError* error = [NSError errorWithDomain: NSOSStatusErrorDomain code: clientValidityStatus userInfo: nil]; + NSLog(@"Rejecting XPC connection because of invalid client signing. Error was %@", error); + [SCSentry captureError: error]; + return NO; + } + + SCDaemonXPC* scdXPC = [[SCDaemonXPC alloc] init]; + newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol: @protocol(SCDaemonProtocol)]; + newConnection.exportedObject = scdXPC; + + [newConnection resume]; + + NSLog(@"Accepted new connection!"); + [SCSentry addBreadcrumb: @"Daemon accepted new connection" category: @"daemon"]; + + return YES; +} + +@end diff --git a/Daemon/SCDaemonBlockMethods.h b/Daemon/SCDaemonBlockMethods.h new file mode 100644 index 00000000..72bfede8 --- /dev/null +++ b/Daemon/SCDaemonBlockMethods.h @@ -0,0 +1,34 @@ +// +// SCDaemonBlockMethods.h +// org.eyebeam.selfcontrold +// +// Created by Charlie Stigler on 7/4/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Top-level logic for different methods run by the SelfControl daemon +// these logics can be run by XPC methods, or elsewhere +@interface SCDaemonBlockMethods : NSObject + +@property (class, readonly) NSLock* daemonMethodLock; + +// Starts a block ++ (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// Checks whether the block is expired or compromised, and takes action to fix ++ (void)checkupBlock; + +// updates the blocklist for the currently running block +// (i.e. adds new sites to the list) ++ (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// updates the block end date for the currently running block +// (i.e. extends the block) ++ (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Daemon/SCDaemonBlockMethods.m b/Daemon/SCDaemonBlockMethods.m new file mode 100644 index 00000000..95e47b24 --- /dev/null +++ b/Daemon/SCDaemonBlockMethods.m @@ -0,0 +1,369 @@ +// +// SCDaemonBlockMethods.m +// org.eyebeam.selfcontrold +// +// Created by Charlie Stigler on 7/4/20. +// + +#import "SCDaemonBlockMethods.h" +#import "SCSettings.h" +#import "SCHelperToolUtilities.h" +#import "PacketFilter.h" +#import "BlockManager.h" +#import "SCDaemon.h" +#import "LaunchctlHelper.h" + +NSTimeInterval METHOD_LOCK_TIMEOUT = 5.0; +NSTimeInterval CHECKUP_LOCK_TIMEOUT = 0.5; // use a shorter lock timeout for checkups, because we'd prefer not to have tons pile up + +@implementation SCDaemonBlockMethods + ++ (NSLock*)daemonMethodLock { + static NSLock* lock = nil; + if (lock == nil) { + lock = [[NSLock alloc] init]; + } + return lock; +} + ++ (BOOL)lockOrTimeout:(void(^)(NSError* error))reply timeout:(NSTimeInterval)timeout { + // only run one request at a time, so we avoid weird situations like trying to run a checkup while we're starting a block + if (![self.daemonMethodLock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow: timeout]]) { + // if we couldn't get a lock within 10 seconds, something is weird + // but we probably shouldn't still run, because that's just unexpected at that point + // don't capture this error on Sentry because it's very usual for checkups to timeout + NSError* err = [SCErr errorWithCode: 300]; + NSLog(@"ERROR: Timed out acquiring request lock (after %f seconds)", timeout); + + if (reply != nil) { + reply(err); + } + return NO; + } + return YES; +} ++ (BOOL)lockOrTimeout:(void(^)(NSError* error))reply { + return [self lockOrTimeout: reply timeout: METHOD_LOCK_TIMEOUT]; +} + + ++ (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + if (![SCDaemonBlockMethods lockOrTimeout: reply]) { + return; + } + + // we reset at the _end_ of every method, but we'll also reset at the _start_ here + // because startBlock can sometimes take a while, and it'd be a shame if the daemon killed itself + // before we were done + [[SCDaemon sharedDaemon] resetInactivityTimer]; + + [SCSentry addBreadcrumb: @"Daemon method startBlock called" category: @"daemon"]; + + if ([SCBlockUtilities anyBlockIsRunning]) { + NSLog(@"ERROR: Can't start block since a block is already running"); + NSError* err = [SCErr errorWithCode: 301]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + // clear any legacy block information - no longer useful and could potentially confuse things + // but first, copy it over one more time (this should've already happened once in the app, but you never know) + if ([SCMigrationUtilities legacySettingsFoundForUser: controllingUID]) { + [SCMigrationUtilities copyLegacySettingsToDefaults: controllingUID]; + [SCMigrationUtilities clearLegacySettingsForUser: controllingUID]; + + // if we had legacy settings, there's a small chance the old helper tool could still be around + // make sure it's dead and gone + [LaunchctlHelper unloadLaunchdJobWithPlistAt: @"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]; + } + + SCSettings* settings = [SCSettings sharedSettings]; + // update SCSettings with the blocklist and end date that've been requested + NSLog(@"Replacing settings end date %@ with %@, and blocklist %@ with %@ (%@ of %@)", [settings valueForKey: @"BlockEndDate"], endDate, [settings valueForKey: @"ActiveBlocklist"], blocklist, [blocklist class], [blocklist[0] class]); + [settings setValue: blocklist forKey: @"ActiveBlocklist"]; + [settings setValue: @(isAllowlist) forKey: @"ActiveBlockAsWhitelist"]; + [settings setValue: endDate forKey: @"BlockEndDate"]; + + // update all the settings for the block, which we're basically just copying from defaults to settings + [settings setValue: blockSettings[@"ClearCaches"] forKey: @"ClearCaches"]; + [settings setValue: blockSettings[@"AllowLocalNetworks"] forKey: @"AllowLocalNetworks"]; + [settings setValue: blockSettings[@"EvaluateCommonSubdomains"] forKey: @"EvaluateCommonSubdomains"]; + [settings setValue: blockSettings[@"IncludeLinkedDomains"] forKey: @"IncludeLinkedDomains"]; + [settings setValue: blockSettings[@"BlockSoundShouldPlay"] forKey: @"BlockSoundShouldPlay"]; + [settings setValue: blockSettings[@"BlockSound"] forKey: @"BlockSound"]; + [settings setValue: blockSettings[@"EnableErrorReporting"] forKey: @"EnableErrorReporting"]; + + if([blocklist count] <= 0 || [SCBlockUtilities currentBlockIsExpired]) { + NSLog(@"ERROR: Blocklist is empty, or block end date is in the past"); + NSLog(@"Block End Date: %@ (%@), vs now is %@", [settings valueForKey: @"BlockEndDate"], [[settings valueForKey: @"BlockEndDate"] class], [NSDate date]); + NSError* err = [SCErr errorWithCode: 302]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + NSLog(@"Adding firewall rules..."); + [SCHelperToolUtilities installBlockRulesFromSettings]; + [settings setValue: @YES forKey: @"BlockIsRunning"]; + + NSError* syncErr = [settings syncSettingsAndWait: 5]; // synchronize ASAP since BlockIsRunning is a really important one + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after starting block", syncErr); + [SCSentry captureError: syncErr]; + } + + NSLog(@"Firewall rules added!"); + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // Clear all caches if the user has the correct preference set, so + // that blocked pages are not loaded from a cache. + [SCHelperToolUtilities clearCachesIfRequested]; + + [SCSentry addBreadcrumb: @"Daemon added block successfully" category: @"daemon"]; + NSLog(@"INFO: Block successfully added."); + reply(nil); + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [[SCDaemon sharedDaemon] startCheckupTimer]; + [self.daemonMethodLock unlock]; +} + ++ (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + if (![SCDaemonBlockMethods lockOrTimeout: reply]) { + return; + } + + [SCSentry addBreadcrumb: @"Daemon method updateBlocklist called" category: @"daemon"]; + if ([SCBlockUtilities legacyBlockIsRunning]) { + NSLog(@"ERROR: Can't update blocklist because a legacy block is running"); + NSError* err = [SCErr errorWithCode: 303]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + if (![SCBlockUtilities modernBlockIsRunning]) { + NSLog(@"ERROR: Can't update blocklist since block isn't running"); + NSError* err = [SCErr errorWithCode: 304]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + SCSettings* settings = [SCSettings sharedSettings]; + + if ([settings boolForKey: @"ActiveBlockAsWhitelist"]) { + NSLog(@"ERROR: Attempting to update active blocklist, but this is not possible with an allowlist block"); + NSError* err = [SCErr errorWithCode: 305]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + NSArray* activeBlocklist = [settings valueForKey: @"ActiveBlocklist"]; + NSMutableArray* added = [NSMutableArray arrayWithArray: newBlocklist]; + [added removeObjectsInArray: activeBlocklist]; + NSMutableArray* removed = [NSMutableArray arrayWithArray: activeBlocklist]; + [removed removeObjectsInArray: newBlocklist]; + + // throw a warning if something got removed for some reason, since we ignore them + if (removed.count > 0) { + NSLog(@"WARNING: Active blocklist has removed items; these will not be updated. Removed items are %@", removed); + } + + BlockManager* blockManager = [[BlockManager alloc] initAsAllowlist: [settings boolForKey: @"ActiveBlockAsWhitelist"] + allowLocal: [settings boolForKey: @"EvaluateCommonSubdomains"] + includeCommonSubdomains: [settings boolForKey: @"AllowLocalNetworks"] + includeLinkedDomains: [settings boolForKey: @"IncludeLinkedDomains"]]; + [blockManager enterAppendMode]; + [blockManager addBlockEntriesFromStrings: added]; + [blockManager finishAppending]; + + [settings setValue: newBlocklist forKey: @"ActiveBlocklist"]; + + // make sure everyone knows about our new list + NSError* syncErr = [settings syncSettingsAndWait: 5]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after updating blocklist", syncErr); + [SCSentry captureError: syncErr]; + } + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // Clear all caches if the user has the correct preference set, so + // that blocked pages are not loaded from a cache. + [SCHelperToolUtilities clearCachesIfRequested]; + + [SCSentry addBreadcrumb: @"Daemon updated blocklist successfully" category: @"daemon"]; + NSLog(@"INFO: Blocklist successfully updated."); + reply(nil); + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [self.daemonMethodLock unlock]; +} + ++ (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + if (![SCDaemonBlockMethods lockOrTimeout: reply]) { + return; + } + + [SCSentry addBreadcrumb: @"Daemon method updateBlockEndDate called" category: @"daemon"]; + + if ([SCBlockUtilities legacyBlockIsRunning]) { + NSLog(@"ERROR: Can't update block end date because a legacy block is running"); + NSError* err = [SCErr errorWithCode: 306]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + if (![SCBlockUtilities modernBlockIsRunning]) { + NSLog(@"ERROR: Can't update block end date since block isn't running"); + NSError* err = [SCErr errorWithCode: 307]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + SCSettings* settings = [SCSettings sharedSettings]; + + // this can only be used to *extend* the block end date - not shorten it! + // and we also won't let them extend by more than 24 hours at a time, for safety... + // TODO: they should be able to extend up to MaxBlockLength minutes, right? + NSDate* currentEndDate = [settings valueForKey: @"BlockEndDate"]; + if ([newEndDate timeIntervalSinceDate: currentEndDate] < 0) { + NSLog(@"ERROR: Can't update block end date to an earlier date"); + NSError* err = [SCErr errorWithCode: 308]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + } + if ([newEndDate timeIntervalSinceDate: currentEndDate] > 86400) { // 86400 seconds = 1 day + NSLog(@"ERROR: Can't extend block end date by more than 1 day at a time"); + NSError* err = [SCErr errorWithCode: 309]; + [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + } + + [settings setValue: newEndDate forKey: @"BlockEndDate"]; + + // make sure everyone knows about our new end date + NSError* syncErr = [settings syncSettingsAndWait: 5]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after extending block", syncErr); + [SCSentry captureError: syncErr]; + } + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + [SCSentry addBreadcrumb: @"Daemon extended block successfully" category: @"daemon"]; + NSLog(@"INFO: Block successfully extended."); + reply(nil); + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [self.daemonMethodLock unlock]; +} + ++ (void)checkupBlock { + if (![SCDaemonBlockMethods lockOrTimeout: nil timeout: CHECKUP_LOCK_TIMEOUT]) { + return; + } + + [SCSentry addBreadcrumb: @"Daemon method checkupBlock called" category: @"daemon"]; + + SCSettings* settings = [SCSettings sharedSettings]; + NSTimeInterval integrityCheckIntervalSecs = 10.0; + static NSDate* lastBlockIntegrityCheck; + if (lastBlockIntegrityCheck == nil) { + lastBlockIntegrityCheck = [NSDate distantPast]; + } + + if(![SCBlockUtilities anyBlockIsRunning]) { + // No block appears to be running at all in our settings. + // Most likely, the user removed it trying to get around the block. Boo! + // but for safety and to avoid permablocks (we no longer know when the block should end) + // we should clear the block now. + // but let them know that we noticed their (likely) cheating and we're not happy! + NSLog(@"INFO: Checkup ran, no active block found."); + + [SCSentry captureMessage: @"Checkup ran and no active block found! Removing block, tampering suspected..."]; + + [SCHelperToolUtilities removeBlock]; + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // Temporarily disabled the TamperingDetection flag because it was sometimes causing false positives + // (i.e. people having the background set repeatedly despite no attempts to cheat) + // We will try to bring this feature back once we can debug it + // GitHub issue: https://github.com/SelfControlApp/selfcontrol/issues/621 + // [settings setValue: @YES forKey: @"TamperingDetected"]; + // [settings synchronizeSettings]; + // + + // once the checkups stop, the daemon will clear itself in a while due to inactivity + [[SCDaemon sharedDaemon] stopCheckupTimer]; + } else if ([SCBlockUtilities currentBlockIsExpired]) { + NSLog(@"INFO: Checkup ran, block expired, removing block."); + + [SCHelperToolUtilities removeBlock]; + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + [SCSentry addBreadcrumb: @"Daemon found and cleared expired block" category: @"daemon"]; + + // once the checkups stop, the daemon will clear itself in a while due to inactivity + [[SCDaemon sharedDaemon] stopCheckupTimer]; + } else if ([[NSDate date] timeIntervalSinceDate: lastBlockIntegrityCheck] > integrityCheckIntervalSecs) { + lastBlockIntegrityCheck = [NSDate date]; + // The block is still on. Every once in a while, we should + // check if anybody removed our rules, and if so + // re-add them. + PacketFilter* pf = [[PacketFilter alloc] init]; + HostFileBlocker* hostFileBlocker = [[HostFileBlocker alloc] init]; + if(![pf containsSelfControlBlock] || (![settings boolForKey: @"ActiveBlockAsWhitelist"] && ![hostFileBlocker containsSelfControlBlock])) { + NSLog(@"INFO: Block is missing in PF or hosts, re-adding..."); + // The firewall is missing at least the block header. Let's clear everything + // before we re-add to make sure everything goes smoothly. + + [pf stopBlock: false]; + + [hostFileBlocker removeSelfControlBlock]; + BOOL success = [hostFileBlocker writeNewFileContents]; + // Revert the host file blocker's file contents to disk so we can check + // whether or not it still contains the block after our write (aka we messed up). + [hostFileBlocker revertFileContentsToDisk]; + if(!success || [hostFileBlocker containsSelfControlBlock]) { + NSLog(@"WARNING: Error removing host file block. Attempting to restore backup."); + + if([hostFileBlocker restoreBackupHostsFile]) + NSLog(@"INFO: Host file backup restored."); + else + NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block."); + } + + // Get rid of the backup file since we're about to make a new one. + [hostFileBlocker deleteBackupHostsFile]; + + // Perform the re-add of the rules + [SCHelperToolUtilities installBlockRulesFromSettings]; + + [SCHelperToolUtilities clearCachesIfRequested]; + + [SCSentry addBreadcrumb: @"Daemon found compromised block integrity and re-added rules" category: @"daemon"]; + NSLog(@"INFO: Checkup ran, readded block rules."); + } else NSLog(@"INFO: Checkup ran with integrity check, no action needed."); + } + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [self.daemonMethodLock unlock]; +} + +@end diff --git a/Daemon/SCDaemonProtocol.h b/Daemon/SCDaemonProtocol.h new file mode 100644 index 00000000..07ccbdef --- /dev/null +++ b/Daemon/SCDaemonProtocol.h @@ -0,0 +1,28 @@ +// +// SCDaemonProtocol.h +// selfcontrold +// +// Created by Charlie Stigler on 5/30/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SCDaemonProtocol + +// XPC method to start block +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// XPC method to add to blocklist +- (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// XPC method to extend block +- (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// XPC method to get version of the installed daemon +- (void)getVersionWithReply:(void(^)(NSString * version))reply; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Daemon/SCDaemonXPC.h b/Daemon/SCDaemonXPC.h new file mode 100644 index 00000000..720d6acf --- /dev/null +++ b/Daemon/SCDaemonXPC.h @@ -0,0 +1,19 @@ +// +// SCDaemonXPC.h +// selfcontrold +// +// Created by Charlie Stigler on 5/30/20. +// + +#import +#import "SCDaemonProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +// Implementations for SC XPC methods +// (see SCDaemonProtocol for all method prototypes) +@interface SCDaemonXPC : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Daemon/SCDaemonXPC.m b/Daemon/SCDaemonXPC.m new file mode 100644 index 00000000..ad9f09c1 --- /dev/null +++ b/Daemon/SCDaemonXPC.m @@ -0,0 +1,77 @@ +// +// SCDaemonXPC.m +// selfcontrold +// +// Created by Charlie Stigler on 5/30/20. +// + +#import "SCDaemonXPC.h" +#import "SCDaemonBlockMethods.h" +#import "SCXPCAuthorization.h" + +@implementation SCDaemonXPC + +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + NSLog(@"XPC method called: startBlockWithControllingUID"); + + NSError* error = [SCXPCAuthorization checkAuthorization: authData command: _cmd]; + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"ERROR: XPC authorization failed due to error %@", error); + [SCSentry captureError: error]; + } + reply(error); + return; + } else { + NSLog(@"AUTHORIZATION ACCEPTED for startBlock with authData %@ and command %s", authData, sel_getName(_cmd)); + } + + [SCDaemonBlockMethods startBlockWithControllingUID: controllingUID blocklist: blocklist isAllowlist:isAllowlist endDate: endDate blockSettings:blockSettings authorization: authData reply: reply]; +} + +- (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + NSLog(@"XPC method called: updateBlocklist"); + + NSError* error = [SCXPCAuthorization checkAuthorization: authData command: _cmd]; + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"ERROR: XPC authorization failed due to error %@", error); + [SCSentry captureError: error]; + } + reply(error); + return; + } else { + NSLog(@"AUTHORIZATION ACCEPTED for updateBlocklist with authData %@ and command %s", authData, sel_getName(_cmd)); + } + + [SCDaemonBlockMethods updateBlocklist: newBlocklist authorization: authData reply: reply]; +} + +- (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + NSLog(@"XPC method called: updateBlockEndDate"); + + NSError* error = [SCXPCAuthorization checkAuthorization: authData command: _cmd]; + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"ERROR: XPC authorization failed due to error %@", error); + [SCSentry captureError: error]; + } + reply(error); + return; + } else { + NSLog(@"AUTHORIZATION ACCEPTED for updateBlockENdDate with authData %@ and command %s", authData, sel_getName(_cmd)); + } + + [SCDaemonBlockMethods updateBlockEndDate: newEndDate authorization: authData reply: reply]; +} + +// Part of the HelperToolProtocol. Returns the version number of the tool. Note that never +// requires authorization. +- (void)getVersionWithReply:(void(^)(NSString * version))reply { + NSLog(@"XPC method called: getVersionWithReply"); + // We specifically don't check for authorization here. Everyone is always allowed to get + // the version of the helper tool. + reply(SELFCONTROL_VERSION_STRING); +} + +@end diff --git a/Daemon/org.eyebeam.selfcontrold.plist b/Daemon/org.eyebeam.selfcontrold.plist new file mode 100644 index 00000000..91dc9b7a --- /dev/null +++ b/Daemon/org.eyebeam.selfcontrold.plist @@ -0,0 +1,19 @@ + + + + + Label + org.eyebeam.selfcontrold + RunAtLoad + + MachServices + + org.eyebeam.selfcontrold + + + KeepAlive + + Nice + 5 + + diff --git a/Daemon/selfcontrold-Info.plist b/Daemon/selfcontrold-Info.plist new file mode 100755 index 00000000..0baed3ff --- /dev/null +++ b/Daemon/selfcontrold-Info.plist @@ -0,0 +1,58 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + selfcontrold + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + selfcontrol + + CFBundleTypeIconFile + SelfControlBlocklist.icns + CFBundleTypeName + SelfControl Blocklist + CFBundleTypeRole + Editor + LSTypeIsPackage + + NSPersistentStoreTypeKey + Binary + + + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + SelfControlIcon + CFBundleIdentifier + org.eyebeam.selfcontrold + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSHumanReadableCopyright + Free and open-source under the GPL. + SMAuthorizedClients + + anchor apple generic and (identifier "org.eyebeam.SelfControl" or identifier "org.eyebeam.selfcontrol-cli") and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = L6W5L88KN7) + + + diff --git a/DomainListWindowController.h b/DomainListWindowController.h index 04116f5b..45b9c4e7 100755 --- a/DomainListWindowController.h +++ b/DomainListWindowController.h @@ -24,7 +24,6 @@ #import #import "HostImporter.h" #import "ThunderbirdPreferenceParser.h" -#import "SelfControlCommon.h" #import "SCSettings.h" // A subclass of NSWindowController created to manage the domain list (actually @@ -36,7 +35,6 @@ IBOutlet NSTableView* domainListTableView_; IBOutlet NSMatrix* allowlistRadioMatrix_; NSUserDefaults* defaults_; - SCSettings* settings_; } // Called when the add button is clicked. Adds a new empty string to the domain diff --git a/DomainListWindowController.m b/DomainListWindowController.m index b1f48ac3..1c9d148d 100755 --- a/DomainListWindowController.m +++ b/DomainListWindowController.m @@ -22,7 +22,6 @@ // along with this program. If not, see . #import "DomainListWindowController.h" -#import "SCUtilities.h" #import "AppController.h" @implementation DomainListWindowController @@ -31,23 +30,21 @@ - (DomainListWindowController*)init { if(self = [super initWithWindowNibName:@"DomainList"]) { defaults_ = [NSUserDefaults standardUserDefaults]; - settings_ = [SCSettings currentUserSettings]; - NSArray* curArray = [settings_ valueForKey: @"Blocklist"]; + NSArray* curArray = [defaults_ arrayForKey: @"Blocklist"]; if(curArray == nil) domainList_ = [NSMutableArray arrayWithCapacity: 10]; else domainList_ = [curArray mutableCopy]; - [settings_ setValue: domainList_ forKey: @"Blocklist"]; + [defaults_ setValue: domainList_ forKey: @"Blocklist"]; } return self; } - (void)awakeFromNib { - NSInteger indexToSelect = [[settings_ valueForKey: @"BlockAsWhitelist"] boolValue] ? 1 : 0; + NSInteger indexToSelect = [defaults_ boolForKey: @"BlockAsWhitelist"] ? 1 : 0; [allowlistRadioMatrix_ selectCellAtRow: indexToSelect column: 0]; - [self updateWindowTitle]; } @@ -57,14 +54,14 @@ - (void)showWindow:(id)sender { if ([domainList_ count] == 0) { [self addDomain: self]; } - + [self updateWindowTitle]; } - (IBAction)addDomain:(id)sender { [domainList_ addObject:@""]; - [settings_ setValue: domainList_ forKey: @"Blocklist"]; + [defaults_ setValue: domainList_ forKey: @"Blocklist"]; [domainListTableView_ reloadData]; NSIndexSet* rowIndex = [NSIndexSet indexSetWithIndex: [domainList_ count] - 1]; [domainListTableView_ selectRowIndexes: rowIndex @@ -92,7 +89,7 @@ - (IBAction)removeDomain:(id)sender index = [selected indexGreaterThanIndex: index]; } - [settings_ setValue: domainList_ forKey: @"Blocklist"]; + [defaults_ setValue: domainList_ forKey: @"Blocklist"]; [domainListTableView_ reloadData]; [[NSNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification" @@ -132,7 +129,7 @@ - (void)tableView:(NSTableView *)aTableView return; } - NSArray* cleanedEntries = [SCUtilities cleanBlocklistEntry: newString]; + NSArray* cleanedEntries = [SCMiscUtilities cleanBlocklistEntry: newString]; for (int i = 0; i < cleanedEntries.count; i++) { NSString* entry = cleanedEntries[i]; @@ -143,7 +140,7 @@ - (void)tableView:(NSTableView *)aTableView } } - [settings_ setValue: domainList_ forKey: @"Blocklist"]; + [defaults_ setValue: domainList_ forKey: @"Blocklist"]; [domainListTableView_ reloadData]; [[NSNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification" object: self]; @@ -154,7 +151,6 @@ - (void)tableView:(NSTableView *)tableView forTableColumn:(NSTableColumn *)tableColumn row:(int)row { // this method is really inefficient. rewrite/optimize later. - [defaults_ synchronize]; // Initialize the cell's text color to black [cell setTextColor: NSColor.textColor]; @@ -236,11 +232,11 @@ - (void)tableView:(NSTableView *)tableView - (IBAction)allowlistOptionChanged:(NSMatrix*)sender { switch (sender.selectedRow) { case 0: - [settings_ setValue: @NO forKey: @"BlockAsWhitelist"]; + [defaults_ setBool: NO forKey: @"BlockAsWhitelist"]; break; case 1: [self showAllowlistWarning]; - [settings_ setValue: @YES forKey: @"BlockAsWhitelist"]; + [defaults_ setBool: YES forKey: @"BlockAsWhitelist"]; break; } @@ -251,20 +247,23 @@ - (IBAction)allowlistOptionChanged:(NSMatrix*)sender { } - (void)showAllowlistWarning { - if(![defaults_ boolForKey: @"WhitelistAlertSuppress"]) { - NSAlert* a = [NSAlert alertWithMessageText: NSLocalizedString(@"Are you sure you want an allowlist block?", @"Allowlist block confirmation prompt") defaultButton: NSLocalizedString(@"OK", @"OK button") alternateButton: @"" otherButton: @"" informativeTextWithFormat: NSLocalizedString(@"An allowlist block means that everything on the internet BESIDES your specified list will be blocked. This includes the web, email, SSH, and anything else your computer accesses via the internet. If a web site requires resources such as images or scripts from a site that is not on your allowlist, the site may not work properly.", @"allowlist block explanation")]; - if([a respondsToSelector: @selector(setShowsSuppressionButton:)]) { - [a setShowsSuppressionButton: YES]; - } - [a runModal]; - if([a respondsToSelector: @selector(suppressionButton)] && [[a suppressionButton] state] == NSOnState) { + if(![defaults_ boolForKey: @"WhitelistAlertSuppress"]) { + NSAlert* alert = [NSAlert new]; + alert.messageText = NSLocalizedString(@"Are you sure you want an allowlist block?", @"Allowlist block confirmation prompt"); + [alert addButtonWithTitle: NSLocalizedString(@"OK", @"OK button")]; + alert.informativeText = NSLocalizedString(@"An allowlist block means that everything on the internet BESIDES your specified list will be blocked. This includes the web, email, SSH, and anything else your computer accesses via the internet. If a web site requires resources such as images or scripts from a site that is not on your allowlist, the site may not work properly.", @"allowlist block explanation"); + alert.showsSuppressionButton = YES; + + [alert runModal]; + + if (alert.suppressionButton.state == NSOnState) { [defaults_ setBool: YES forKey: @"WhitelistAlertSuppress"]; } } } - (void)updateWindowTitle { - NSString* listType = [[settings_ valueForKey: @"BlockAsWhitelist"] boolValue] ? @"Allowlist" : @"Blocklist"; + NSString* listType = [defaults_ boolForKey: @"BlockAsWhitelist"] ? @"Allowlist" : @"Blocklist"; self.window.title = NSLocalizedString(([NSString stringWithFormat: @"Domain %@", listType]), @"Domain list window title"); } @@ -274,7 +273,7 @@ - (void)addHostArray:(NSArray*)arr { if(![domainList_ containsObject: arr[i]]) [domainList_ addObject: arr[i]]; } - [settings_ setValue: domainList_ forKey: @"Blocklist"]; + [defaults_ setValue: domainList_ forKey: @"Blocklist"]; [domainListTableView_ reloadData]; [[NSNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification" object: self]; diff --git a/ERRORS b/ERRORS deleted file mode 100755 index 7689e4d4..00000000 --- a/ERRORS +++ /dev/null @@ -1,31 +0,0 @@ --101 - Attempting to add block, but a block appears to be in progress. --102 - Attempting to add block, but no blocklist is set. --103 - Attempting to add host to block, but no block appears to be in progress. --104 - The helper tool crashed. This may cause unexpected errors. --105 - The helper tool crashed. This may cause unexpected errors. - --201 - Helper tool not launched as root. --202 - Helper tool launched with insufficient arguments. --203 - Host blocklist not set --204 - Could not write launchd plist file to LaunchDaemons folder. --205 - Could not create PrivilegedHelperTools directory. --206 - Could not change permissions on PrivilegedHelperTools directory. --207 - Could not delete old helper binary. --208 - Could not copy SelfControl's helper binary to PrivilegedHelperTools directory. --209 - Could not change permissions on SelfControl's helper binary. --210 - Insufficient block information found. --211 - Launch daemon load returned a failure status code. --212 - Remove option called --213 - Refreshing domain blocklist, but no block is currently ongoing. --214 - Insufficient block information found. --215 - Checkup ran but no block found. --216 - Could not write lock file. --217 - Could not write lock file. --218 - Could not remove lock file. --219 - Trying to add block but lock file already found. Please try again. --220 - Block end date argument is invalid --221 - Block could not be read from file --221 - Block is already running (so we can't start another) - --901 - Selected sound not found. --902 - Error serializing blocklist for save diff --git a/HelperCommon.h b/HelperCommon.h deleted file mode 100644 index dcbd51da..00000000 --- a/HelperCommon.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// HelperCommonFunctions.h -// SelfControl -// -// Created by Charlie Stigler on 07/13/09. -// Copyright 2009 Eyebeam. - -// This file is part of SelfControl. -// -// SelfControl is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// All the functions etc. formerly used for HelperMain are now separated into this file. -// This is so that another helper tool can be created that easily uses those same functions. - -// imports for all helper tools -#import -#import -#import "LaunchctlHelper.h" -#import -#import -#import -#import "HostFileBlocker.h" -#import "SelfControlCommon.h" -#import "SCUtilities.h" -#import "SCSettings.h" - -// Uses seteuid to check both settings and defaults for whether the block is running, or not -BOOL blockIsRunningInSettingsOrDefaults(uid_t controllingUID); - -// Reads the domain block list from the settings for SelfControl, and adds deny -// rules for all of the IPs (or the A DNS record IPS for doamin names) to the -// ipfw firewall. -void addRulesToFirewall(uid_t controllingUID); - -// Removes from ipfw all rules that were created by SelfControl. -void removeRulesFromFirewall(uid_t controllingUID); - -// Returns an autoreleased NSSet containing all IP adresses for evaluated -// "common subdomains" for the specified hostname -NSSet* getEvaluatedHostNamesFromCommonSubdomains(NSString* hostName, int port); - -// Checks the settings system to see whether the user wants their web browser -// caches cleared, and deletes the specific cache folders for a few common -// web browsers if it is required. -void clearCachesIfRequested(uid_t controllingUID); - -// Clear only the caches for browsers -void clearBrowserCaches(uid_t controllingUID); - -// Clear only the OS-level DNS cache -void clearOSDNSCache(void); - -// Prints out the given status code to stdout using printf -void printStatus(int status); - -// Removes block via settings, host file rules and ipfw rules, unloading the -// org.eyebeam.SelfControl item, deleting user caches if requested, and migrating legacy settings. -void removeBlock(uid_t controllingUID); - -void sendConfigurationChangedNotification(void); - -// makes sure the secured settings finish syncing -// before calling exit with the given status -// if there's any chance settings could have been modified, we should -// always call this instead of plain exit() to make sure settings aren't left unsynced! -// this waits for the settings to sync before returning, so may be slow -void syncSettingsAndExit(SCSettings* settings, int status); diff --git a/HelperCommon.m b/HelperCommon.m deleted file mode 100644 index 3b830db1..00000000 --- a/HelperCommon.m +++ /dev/null @@ -1,258 +0,0 @@ -/* - * HelperCommonFunctions.c - * SelfControl - * - * Created by Charlie Stigler on 7/13/10. - * Copyright 2010 Harvard-Westlake Student. All rights reserved. - * - */ - -#include "HelperCommon.h" -#include "BlockManager.h" -#import "SCUtilities.h" -#import "SCSettings.h" -#import "SCConstants.h" - -BOOL blockIsRunningInSettingsOrDefaults(uid_t controllingUID) { - SCSettings* settings = [SCSettings settingsForUser: controllingUID]; - - // pull up the user's defaults to check for the existence of a legacy block - // to do that, we have to seteuid to the controlling UID so NSUserDefaults thinks we're them - seteuid(controllingUID); - NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; - [defaults addSuiteNamed: @"org.eyebeam.SelfControl"]; - [defaults synchronize]; - - BOOL response = [SCUtilities blockIsRunningWithSettings: settings defaults: defaults]; - - // reset the euid so nothing else gets funky - [NSUserDefaults resetStandardUserDefaults]; - seteuid(0); - - return response; -} - -void addRulesToFirewall(uid_t controllingUID) { - SCSettings* settings = [SCSettings settingsForUser: controllingUID]; - BOOL shouldEvaluateCommonSubdomains = [[settings valueForKey: @"EvaluateCommonSubdomains"] boolValue]; - BOOL allowLocalNetworks = [[settings valueForKey: @"AllowLocalNetworks"] boolValue]; - BOOL includeLinkedDomains = [[settings valueForKey: @"IncludeLinkedDomains"] boolValue]; - - // get value for BlockAsWhitelist - BOOL blockAsAllowlist = [[settings valueForKey: @"BlockAsWhitelist"] boolValue]; - - BlockManager* blockManager = [[BlockManager alloc] initAsAllowlist: blockAsAllowlist allowLocal: allowLocalNetworks includeCommonSubdomains: shouldEvaluateCommonSubdomains includeLinkedDomains: includeLinkedDomains]; - - [blockManager prepareToAddBlock]; - [blockManager addBlockEntries: [settings valueForKey: @"Blocklist"]]; - [blockManager finalizeBlock]; - -} - -void removeRulesFromFirewall(uid_t controllingUID) { - // options don't really matter because we're only using it to clear - BlockManager* blockManager = [[BlockManager alloc] init]; - [blockManager clearBlock]; - - // We'll play the sound now rather than earlier, because - // it is important that the UI get updated (by the posted - // notification) before we sleep to play the sound. Otherwise, - // the app seems unresponsive and slow. - SCSettings* settings = [SCSettings settingsForUser: controllingUID]; - if([[settings valueForKey: @"BlockSoundShouldPlay"] boolValue]) { - // Map the tags used in interface builder to the sound - NSArray* systemSoundNames = [SCConstants systemSoundNames]; - NSSound* alertSound = [NSSound soundNamed: systemSoundNames[[[settings valueForKey: @"BlockSound"] intValue]]]; - if(!alertSound) - NSLog(@"WARNING: Alert sound not found."); - else { - [alertSound play]; - // Sleeping a second is a messy way of doing this, but otherwise the - // sound is killed along with this process when it is unloaded in just - // a few lines. - sleep(1); - } - } -} - -NSSet* getEvaluatedHostNamesFromCommonSubdomains(NSString* hostName, int port) { - NSMutableSet* evaluatedAddresses = [NSMutableSet set]; - - // If the domain ends in facebook.com... Special case for Facebook because - // users will often forget to block some of its many mirror subdomains that resolve - // to different IPs, i.e. hs.facebook.com. Thanks to Danielle for raising this issue. - if([hostName rangeOfString: @"facebook.com"].location == ([hostName length] - 12)) { - [evaluatedAddresses addObject: @"69.63.176.0/20"]; - } - - // Block the domain with no subdomains, if www.domain is blocked - else if([hostName rangeOfString: @"www."].location == 0) { - NSHost* modifiedHost = [NSHost hostWithName: [hostName substringFromIndex: 4]]; - - if(modifiedHost) { - NSArray* addresses = [modifiedHost addresses]; - - for(int j = 0; j < [addresses count]; j++) { - if(port != -1) - [evaluatedAddresses addObject: [NSString stringWithFormat: @"%@:%d", addresses[j], port]]; - else [evaluatedAddresses addObject: addresses[j]]; - } - } - } - // Or block www.domain otherwise - else { - NSHost* modifiedHost = [NSHost hostWithName: [@"www." stringByAppendingString: hostName]]; - - if(modifiedHost) { - NSArray* addresses = [modifiedHost addresses]; - - for(int j = 0; j < [addresses count]; j++) { - if(port != -1) - [evaluatedAddresses addObject: [NSString stringWithFormat: @"%@:%d", addresses[j], port]]; - else [evaluatedAddresses addObject: addresses[j]]; - } - } - } - - return evaluatedAddresses; -} - -void clearCachesIfRequested(uid_t controllingUID) { - SCSettings* settings = [SCSettings settingsForUser: controllingUID]; - if(![[settings valueForKey: @"ClearCaches"] boolValue]) { - return; - } - - clearBrowserCaches(controllingUID); - clearOSDNSCache(); -} - -void clearBrowserCaches(uid_t controllingUID) { - NSFileManager* fileManager = [NSFileManager defaultManager]; - - // need to seteuid so the tilde expansion will work properly - seteuid(controllingUID); - NSString* libraryDirectoryExpanded = [@"~/Library" stringByExpandingTildeInPath]; - seteuid(0); - - NSArray* cacheDirs = @[ - // chrome - @"/Caches/Google/Chrome/Default", - @"/Caches/Google/Chrome/com.google.Chrome", - - // firefox - @"/Caches/Firefox/Profiles", - - // safari - @"/Caches/com.apple.Safari", - @"/Containers/com.apple.Safari/Data/Library/Caches" // this one seems to fail due to permissions issues, but not sure how to fix - ]; - for (NSString* cacheDir in cacheDirs) { - NSString* absoluteCacheDir = [libraryDirectoryExpanded stringByAppendingString: cacheDir]; - NSLog(@"Clearing browser cache folder %@", absoluteCacheDir); - [fileManager removeItemAtPath: absoluteCacheDir error: nil]; - } -} - -void clearOSDNSCache() { - // no error checks - if it works it works! - NSTask* flushDsCacheUtil = [[NSTask alloc] init]; - [flushDsCacheUtil setLaunchPath: @"/usr/bin/dscacheutil"]; - [flushDsCacheUtil setArguments: @[@"-flushcache"]]; - [flushDsCacheUtil launch]; - [flushDsCacheUtil waitUntilExit]; - - NSTask* killResponder = [[NSTask alloc] init]; - [killResponder setLaunchPath: @"/usr/bin/killall"]; - [killResponder setArguments: @[@"-HUP", @"mDNSResponder"]]; - [killResponder launch]; - [killResponder waitUntilExit]; - - NSTask* killResponderHelper = [[NSTask alloc] init]; - [killResponderHelper setLaunchPath: @"/usr/bin/killall"]; - [killResponderHelper setArguments: @[@"mDNSResponderHelper"]]; - [killResponderHelper launch]; - [killResponderHelper waitUntilExit]; - - NSLog(@"Cleared OS DNS caches"); -} - -void printStatus(int status) { - printf("%d", status); - fflush(stdout); -} - -void removeBlock(uid_t controllingUID) { - SCSettings* settings = [SCSettings settingsForUser: controllingUID]; - - [SCUtilities removeBlockFromSettingsForUID: controllingUID]; - removeRulesFromFirewall(controllingUID); - - // go ahead and remove any remaining legacy block info at the same time to avoid confusion - // (and migrate them to the new SCSettings system if not already migrated) - [settings clearLegacySettings]; - - // always synchronize settings ASAP after removing a block to let everybody else know - [settings synchronizeSettings]; - - // let the main app know things have changed so it can update the UI! - sendConfigurationChangedNotification(); - - NSLog(@"INFO: Block cleared."); - - clearCachesIfRequested(controllingUID); - - // the final step is to unload the launchd job - // this will kill this process, so we have to make sure - // all settings are synced before we unload - [settings synchronizeSettingsWithCompletion:^(NSError* err) { - if (err != nil) { - NSLog(@"WARNING: Settings failed to synchronize before unloading block, with error %@", err); - } - - [LaunchctlHelper unloadLaunchdJobWithPlistAt:@"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]; - }]; - - // wait 5 seconds. assuming the synchronization completes during that time, - // it'll unload the launchd job for us and we'll never get to the other side of this wait - sleep(5); - - // uh-oh, looks like it's 5 seconds later and the sync hasn't completed yet. Bad news. - NSLog(@"WARNING: Settings sync timed out before unloading block"); - [LaunchctlHelper unloadLaunchdJobWithPlistAt:@"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]; -} - -void sendConfigurationChangedNotification() { - // if you don't include the NSNotificationPostToAllSessions option, - // it will not deliver when run by launchd (root) to the main app being run by the user - [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification" - object: nil - userInfo: nil - options: NSNotificationDeliverImmediately | NSNotificationPostToAllSessions]; -} - -void syncSettingsAndExit(SCSettings* settings, int status) { - // this should always be run on the main thread so it blocks main() - if (![NSThread isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), ^{ - syncSettingsAndExit(settings, status); - }); - } - - [settings synchronizeSettingsWithCompletion:^(NSError* err) { - if (err != nil) { - NSLog(@"WARNING: Settings failed to synchronize before exit, with error %@", err); - } - - exit(status); - }]; - - // wait 5 seconds. assuming the synchronization completes during that time, - // it'll exit() for us and we'll never get to the other side of this wait - sleep(5); - - // uh-oh, looks like it's 5 seconds later and the sync hasn't completed yet. Bad news. - NSLog(@"WARNING: Settings sync timed out before exiting"); - - exit(status); -} diff --git a/HelperMain.h b/HelperMain.h deleted file mode 100755 index b3593ce3..00000000 --- a/HelperMain.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// HelperMain.h -// SelfControl -// -// Created by Charlie Stigler on 2/4/09. -// Copyright 2009 Eyebeam. - -// This file is part of SelfControl. -// -// SelfControl is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// The main class for SelfControl's helper tool to be run by launchd with high -// privileges in order to handle the root-only configuration. - -#import "PacketFilter.h" -#import "HelperCommon.h" - -// The main method which deals which most of the logic flow and execution of -// the helper tool. Posts an SCConfigurationChangedNotification if the block -// is enabled or disabled. -int main(int argc, char* argv[]); diff --git a/HelperMain.m b/HelperMain.m deleted file mode 100755 index b24f66fe..00000000 --- a/HelperMain.m +++ /dev/null @@ -1,332 +0,0 @@ -// -// helpermain.m -// SelfControl -// -// Created by Charlie Stigler on 2/4/09. -// Copyright 2009 Eyebeam. - -// This file is part of SelfControl. -// -// SelfControl is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#import "HelperMain.h" -#import "SCUtilities.h" -#import "SCSettings.h" -#import "version-header.h" - -int main(int argc, char* argv[]) { - @autoreleasepool { - - if(geteuid()) { - NSLog(@"ERROR: SelfControl's helper tool must be run as root."); - printStatus(-201); - exit(EX_NOPERM); - } - - setuid(0); - - if(argc < 3 || argv[1] == NULL || argv[2] == NULL) { - NSLog(@"ERROR: Not enough arguments"); - printStatus(-202); - exit(EX_USAGE); - } - - NSString* modeString = @(argv[2]); - // We'll need the controlling UID to know what settings to read - uid_t controllingUID = [@(argv[1]) intValue]; - - // For proper security, we need to make sure that SelfControl files are owned - // by root and only writable by root. We'll define this here so we can use it - // throughout the main function. - NSDictionary* fileAttributes = @{NSFileOwnerAccountID: @0UL, - NSFileGroupOwnerAccountID: @0UL, - // 493 (decimal) = 755 (octal) = rwxr-xr-x - NSFilePosixPermissions: @493UL}; - - - SCSettings* settings = [SCSettings settingsForUser: controllingUID]; - - if([modeString isEqual: @"--install"]) { - if (blockIsRunningInSettingsOrDefaults(controllingUID)) { - NSLog(@"ERROR: Block is already running"); - printStatus(-222); - exit(EX_CONFIG); - } - - NSFileManager* fileManager = [NSFileManager defaultManager]; - - // Initialize writeErr to nil so calling messages on it later don't cause - // crashes (it doesn't make sense we need to do this, but whatever). - NSError* writeErr = nil; - NSString* plistFormatPath = [[NSBundle mainBundle] pathForResource:@"org.eyebeam.SelfControl" - ofType:@"plist"]; - NSString* plistFormatString = [NSString stringWithContentsOfFile: plistFormatPath encoding: NSUTF8StringEncoding error: NULL]; - - // while the main app will just expect us to fetch the blocklist / end date from settings - // this helper tool can also be used via command line with no other settings - // (ex: by auto-selfcontrol) and in that case they'll pass a blocklist file and the end date via args - // we should read those values into settings for use later - NSString* pathToBlocklistFile; - NSDate* blockEndDateArg; - if (argv[3] != NULL && argv[4] != NULL) { - pathToBlocklistFile = @(argv[3]); - blockEndDateArg = [[NSISO8601DateFormatter new] dateFromString: @(argv[4])]; - - // if we didn't get a valid block end date in the future, ignore the other args - if (blockEndDateArg == nil || [blockEndDateArg timeIntervalSinceNow] < 1) { - pathToBlocklistFile = nil; - NSLog(@"Error: Block end date argument %@ is invalid", @(argv[4])); - printStatus(-220); - syncSettingsAndExit(settings, EX_IOERR); - } else { - [settings setValue: blockEndDateArg forKey: @"BlockEndDate"]; - BOOL readSuccess = [SCUtilities readBlocklistFromFile: [NSURL fileURLWithPath: pathToBlocklistFile] toSettings: settings]; - - if (!readSuccess) { - NSLog(@"ERROR: Block could not be read from file %@", pathToBlocklistFile); - printStatus(-221); - syncSettingsAndExit(settings, EX_IOERR); - } - } - } - - // get the expiration minute, to make sure we run the helper then (if it hasn't run already) - // use the block end date from the argument if it's available; otherwise fall back to the one in settings - NSDate* blockEndDate = (blockEndDateArg != nil) ? blockEndDateArg : [settings valueForKey: @"BlockEndDate"]; - NSCalendar* calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; - NSDateComponents* components = [calendar components: NSMinuteCalendarUnit fromDate: blockEndDate]; - long expirationMinute = [components minute]; - - NSString* plistString = [NSString stringWithFormat: - plistFormatString, - MAX(expirationMinute - 1, 0), - expirationMinute, - MIN(expirationMinute + 1, 59), - controllingUID]; - [plistString writeToFile: @"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist" - atomically: YES - encoding: NSUTF8StringEncoding - error: &writeErr]; - - if([writeErr code]) { - NSLog(@"ERROR: Could not write launchd plist file to LaunchDaemons folder."); - printStatus(-204); - syncSettingsAndExit(settings, EX_IOERR); - } - - if(![fileManager fileExistsAtPath: @"/Library/PrivilegedHelperTools"]) { - if(![fileManager createDirectoryAtPath: @"/Library/PrivilegedHelperTools" - withIntermediateDirectories: NO - attributes: fileAttributes - error: nil]) { - NSLog(@"ERROR: Could not create PrivilegedHelperTools directory."); - printStatus(-205); - syncSettingsAndExit(settings, EX_IOERR); - } - } else { - if(![fileManager setAttributes: fileAttributes ofItemAtPath: @"/Library/PrivilegedHelperTools" error: nil]) { - NSLog(@"ERROR: Could not change permissions on PrivilegedHelperTools directory."); - printStatus(-206); - syncSettingsAndExit(settings, EX_IOERR); - } - } - // We should delete the old file if it exists and copy the new binary in, - // because it might be a new version of the helper if we've upgraded SelfControl - if([fileManager fileExistsAtPath: @"/Library/PrivilegedHelperTools/org.eyebeam.SelfControl"]) { - if(![fileManager removeItemAtPath: @"/Library/PrivilegedHelperTools/org.eyebeam.SelfControl" error: nil]) { - NSLog(@"ERROR: Could not delete old helper binary."); - printStatus(-207); - syncSettingsAndExit(settings, EX_IOERR); - } - } - - if(![fileManager copyItemAtPath: @(argv[0]) - toPath: @"/Library/PrivilegedHelperTools/org.eyebeam.SelfControl" - error: nil]) { - NSLog(@"ERROR: Could not copy SelfControl's helper binary to PrivilegedHelperTools directory."); - printStatus(-208); - syncSettingsAndExit(settings, EX_IOERR); - } - - if(![fileManager setAttributes: fileAttributes ofItemAtPath: @"/Library/PrivilegedHelperTools/org.eyebeam.SelfControl" error: nil]) { - NSLog(@"ERROR: Could not change permissions on SelfControl's helper binary."); - printStatus(-209); - syncSettingsAndExit(settings, EX_IOERR); - } - - // clear any legacy block information - no longer useful since we're using SCSettings now - // (and could potentially confuse things) - [settings clearLegacySettings]; - - if([[settings valueForKey: @"Blocklist"] count] <= 0 || ![SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]) { - NSLog(@"ERROR: Blocklist is empty, or there was an error transferring block information."); - NSLog(@"Block End Date: %@", [settings valueForKey: @"BlockEndDate"]); - printStatus(-210); - syncSettingsAndExit(settings, EX_CONFIG); - } - - addRulesToFirewall(controllingUID); - [settings setValue: @YES forKey: @"BlockIsRunning"]; - [settings synchronizeSettings]; // synchronize ASAP since BlockIsRunning is a really important one - - // first unload any old running copies, because otherwise we could end up with an old - // version of the SC plist running with a newer version of the app - // (calling load doesn't update the existing job if it's already running) - [LaunchctlHelper unloadLaunchdJobWithPlistAt:@"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]; - int result = [LaunchctlHelper loadLaunchdJobWithPlistAt: @"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]; - - sendConfigurationChangedNotification(); - - // Clear all caches if the user has the correct preference set, so - // that blocked pages are not loaded from a cache. - clearCachesIfRequested(controllingUID); - - if(result) { - printStatus(-211); - NSLog(@"WARNING: Launch daemon load returned a failure status code."); - syncSettingsAndExit(settings, EX_UNAVAILABLE); - } else NSLog(@"INFO: Block successfully added."); - } - if([modeString isEqual: @"--remove"]) { - // So you think you can rid yourself of SelfControl just like that? - NSLog(@"INFO: Nice try."); - printStatus(-212); - syncSettingsAndExit(settings, EX_UNAVAILABLE); - } else if([modeString isEqual: @"--refresh"]) { - // used when the blocklist may have changed, to make sure we are blocking the new list - - if([[settings valueForKey: @"Blocklist"] count] <= 0 || ![SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]) { - NSLog(@"ERROR: Refreshing domain blocklist, but no block is currently ongoing or the blocklist is empty."); - printStatus(-213); - syncSettingsAndExit(settings, EX_SOFTWARE); - } - - // Add and remove the rules to put in any new ones - removeRulesFromFirewall(controllingUID); - addRulesToFirewall(controllingUID); - - // make sure BlockIsRunning is still set - [settings setValue: @YES forKey: @"BlockIsRunning"]; - [settings synchronizeSettings]; - - // let the main app know things have changed so it can update the UI! - sendConfigurationChangedNotification(); - - // make sure the launchd job is still loaded - [LaunchctlHelper loadLaunchdJobWithPlistAt: @"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]; - - // Clear web browser caches if the user has the correct preference set. We - // need to do this again even if it's only a refresh because there might be - // caches for the new host blocked. - clearCachesIfRequested(controllingUID); - } else if([modeString isEqual: @"--checkup"]) { - if(![SCUtilities blockIsRunningInDictionary: settings.dictionaryRepresentation]) { - // No block appears to be running at all in our settings. - // Most likely, the user removed it trying to get around the block. Boo! - // but for safety and to avoid permablocks (we no longer know when the block should end) - // we should clear the block now. - // but let them know that we noticed their (likely) cheating and we're not happy! - NSLog(@"ERROR: Checkup ran but no block found. Likely tampering! Removing block for safety, but flagging tampering."); - - // get rid of this block - // Temporarily disabled the TamperingDetection flag because it was sometimes causing false positives - // (i.e. people having the background set repeatedly despite no attempts to cheat) - // We will try to bring this feature back once we can debug it - // GitHub issue: https://github.com/SelfControlApp/selfcontrol/issues/621 - // [settings setValue: @YES forKey: @"TamperingDetected"]; - [settings synchronizeSettings]; - - removeBlock(controllingUID); - - printStatus(-215); - syncSettingsAndExit(settings, EX_SOFTWARE); - } - - if (![SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]) { - NSLog(@"INFO: Checkup ran, block expired, removing block."); - - removeBlock(controllingUID); - - // Execution should never reach this point. Launchd unloading the job in removeBlock() - // should have killed this process. - printStatus(-216); - syncSettingsAndExit(settings, EX_SOFTWARE); - } else { - // The block is still on. Check if anybody removed our rules, and if so - // re-add them. Also make sure the user's settings are set to the correct - // settings just in case. - PacketFilter* pf = [[PacketFilter alloc] init]; - HostFileBlocker* hostFileBlocker = [[HostFileBlocker alloc] init]; - if(![pf containsSelfControlBlock] || (![[settings valueForKey: @"BlockAsWhitelist"] boolValue] && ![hostFileBlocker containsSelfControlBlock])) { - // The firewall is missing at least the block header. Let's clear everything - // before we re-add to make sure everything goes smoothly. - - [pf stopBlock: false]; - [hostFileBlocker writeNewFileContents]; - BOOL success = [hostFileBlocker writeNewFileContents]; - // Revert the host file blocker's file contents to disk so we can check - // whether or not it still contains the block (aka we messed up). - [hostFileBlocker revertFileContentsToDisk]; - if(!success || [hostFileBlocker containsSelfControlBlock]) { - NSLog(@"WARNING: Error removing host file block. Attempting to restore backup."); - - if([hostFileBlocker restoreBackupHostsFile]) - NSLog(@"INFO: Host file backup restored."); - else - NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block."); - } - - // Get rid of the backup file since we're about to make a new one. - [hostFileBlocker deleteBackupHostsFile]; - - // Perform the re-add of the rules - addRulesToFirewall(controllingUID); - - clearCachesIfRequested(controllingUID); - NSLog(@"INFO: Checkup ran, readded block rules."); - } else NSLog(@"INFO: Checkup ran, no action needed."); - } - } else if ([modeString isEqualToString: @"--print-settings"]) { - NSLog(@" - Printing SelfControl secured settings for debug: - "); - NSLog(@"%@", [settings dictionaryRepresentation]); - } else if ([modeString isEqualToString: @"--is-running"]) { - // pull up the user's defaults to check for the existence of a legacy block - // to do that, we have to seteuid to the controlling UID so NSUserDefaults thinks we're them - seteuid(controllingUID); - NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; - [defaults addSuiteNamed: @"org.eyebeam.SelfControl"]; - [defaults synchronize]; - - BOOL blockIsRunning = [SCUtilities blockIsRunningWithSettings: settings defaults: defaults]; - NSLog(@"%@", blockIsRunning ? @"YES" : @"NO"); - - // reset the euid so nothing else gets funky - [NSUserDefaults resetStandardUserDefaults]; - seteuid(0); - } else if ([modeString isEqualToString: @"--version"]) { - NSLog(SELFCONTROL_VERSION_STRING); - } - - // by putting printStatus first (which tells the app we didn't crash), we fake it to - // avoid memory-managment crashes (calling [pool drain] is essentially optional) - printStatus(0); - - // final sync before we exit - syncSettingsAndExit(settings, EXIT_SUCCESS); - } - - // wait, how'd we get out of the autorelease block without hitting the exit just above this? - // whoops, something broke - exit(EX_SOFTWARE); -} diff --git a/Info.plist b/Info.plist index bcdada61..8d7323ca 100755 --- a/Info.plist +++ b/Info.plist @@ -38,9 +38,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - $(MARKETING_VERSION) + 4.0 alpha 1 CFBundleVersion - $(CURRENT_PROJECT_VERSION) + 399 LSApplicationCategoryType public.app-category.productivity LSHasLocalizedDisplayName @@ -55,6 +55,11 @@ MainMenu NSPrincipalClass NSApplication + SMPrivilegedExecutables + + org.eyebeam.selfcontrold + anchor apple generic and identifier "org.eyebeam.selfcontrold" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = L6W5L88KN7) + SUEnableAutomaticChecks SUEnableJavaScript diff --git a/LaunchctlHelper.h b/LaunchctlHelper.h index 213e673c..3dec6a77 100755 --- a/LaunchctlHelper.h +++ b/LaunchctlHelper.h @@ -27,12 +27,6 @@ @interface LaunchctlHelper : NSObject { } -// Calls the launchctl command-line tool installed on all newer Mac OS X -// systems to load into the launchd system a new job. The specifications -// for this job are provided in the plist at the given path. Returns the exit -// status code of launchctl. -+ (int)loadLaunchdJobWithPlistAt:(NSString*)pathToLaunchdPlist; - // Calls the launchctl command-line tool installed on all newer Mac OS X // systems to unload a job from the launchd system, which was loaded from the // plist at the given path. Returns the exit status code of launchctl. diff --git a/LaunchctlHelper.m b/LaunchctlHelper.m index 5c50a119..2269fb98 100755 --- a/LaunchctlHelper.m +++ b/LaunchctlHelper.m @@ -24,17 +24,6 @@ @implementation LaunchctlHelper -+ (int)loadLaunchdJobWithPlistAt:(NSString*)pathToLaunchdPlist { - NSTask* task = [[NSTask alloc] init]; - [task setLaunchPath: @"/bin/launchctl"]; - [task setArguments: @[@"load", - @"-w", - pathToLaunchdPlist]]; - [task launch]; - [task waitUntilExit]; - return [task terminationStatus]; -} - + (int)unloadLaunchdJobWithPlistAt:(NSString*)pathToLaunchdPlist { NSTask* task = [[NSTask alloc] init]; [task setLaunchPath: @"/bin/launchctl"]; diff --git a/NSString+IPAddress.h b/NSString+IPAddress.h index b9f1bc21..36c52360 100644 --- a/NSString+IPAddress.h +++ b/NSString+IPAddress.h @@ -23,6 +23,7 @@ #import #include +// methods to check whether the string is a valid IP address @interface NSString (IPAddress) @property (nonatomic, getter=isValidIPv4Address, readonly) BOOL validIPv4Address; diff --git a/Podfile b/Podfile index 9206b537..d62d5e72 100644 --- a/Podfile +++ b/Podfile @@ -1,13 +1,45 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :osx, '10.8' + +minVersion = '10.10' + +platform :osx, minVersion # cocoapods-prune-localizations doesn't appear to auto-detect pods properly, so using a manual list supported_locales = ['Base', 'da', 'de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'nl', 'pt-BR', 'sv', 'tr', 'zh-Hans'] plugin 'cocoapods-prune-localizations', { :localizations => supported_locales } target "SelfControl" do + use_frameworks! :linkage => :static pod 'MASPreferences', '~> 1.1.4' pod 'FormatterKit/TimeIntervalFormatter', '~> 1.8.0' - pod 'Sparkle', '~> 1.22' pod 'LetsMove', '~> 1.24' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' +end + +target "SelfControl Killer" do + use_frameworks! :linkage => :static + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' +end + +# we can't use_frameworks on these because they're command-line tools +# Sentry says we need use_frameworks, but they seem to work OK anyway? +target "SCKillerHelper" do + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' +end +target "selfcontrol-cli" do + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' +end +target "org.eyebeam.selfcontrold" do + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' +end + +post_install do |pi| + pi.pods_project.targets.each do |t| + t.build_configurations.each do |bc| + if Gem::Version.new(bc.build_settings['MACOSX_DEPLOYMENT_TARGET']) < Gem::Version.new(minVersion) +# if bc.build_settings['MACOSX_DEPLOYMENT_TARGET'] == '8.0' + bc.build_settings['MACOSX_DEPLOYMENT_TARGET'] = minVersion + end + end + end end diff --git a/PreferencesAdvancedViewController.h b/PreferencesAdvancedViewController.h index 683fe7c0..63ade777 100644 --- a/PreferencesAdvancedViewController.h +++ b/PreferencesAdvancedViewController.h @@ -10,10 +10,5 @@ #import "MASPreferencesViewController.h" @interface PreferencesAdvancedViewController : NSViewController - -@property IBOutlet NSButton* clearCachesCheckbox; -@property IBOutlet NSButton* allowLocalCheckbox; -@property IBOutlet NSButton* includeSubdomainsCheckbox; -@property IBOutlet NSButton* includeLinkedSitesCheckbox; @end diff --git a/PreferencesAdvancedViewController.m b/PreferencesAdvancedViewController.m index 24d3acc8..ec763d80 100644 --- a/PreferencesAdvancedViewController.m +++ b/PreferencesAdvancedViewController.m @@ -20,42 +20,6 @@ - (instancetype)init { return [super initWithNibName: @"PreferencesAdvancedViewController" bundle: nil]; } -- (void)refreshFromSecuredSettings { - SCSettings* settings = [SCSettings currentUserSettings]; - BOOL clearCaches = [[settings valueForKey: @"ClearCaches"] boolValue]; - BOOL allowLocalNetworks = [[settings valueForKey: @"AllowLocalNetworks"] boolValue]; - BOOL evaluateCommonSubdomains = [[settings valueForKey: @"EvaluateCommonSubdomains"] boolValue]; - BOOL includeLinkedDomains = [[settings valueForKey: @"IncludeLinkedDomains"] boolValue]; - - self.clearCachesCheckbox.state = clearCaches; - self.allowLocalCheckbox.state = allowLocalNetworks; - self.includeSubdomainsCheckbox.state = evaluateCommonSubdomains; - self.includeLinkedSitesCheckbox.state = includeLinkedDomains; -} - -- (IBAction)securedCheckboxChanged:(NSButton*)sender { - BOOL isChecked = (((NSButton*)sender).state == NSOnState); - SCSettings* settings = [SCSettings currentUserSettings]; - - if (sender == self.clearCachesCheckbox) { - [settings setValue: @(isChecked) forKey: @"ClearCaches"]; - } else if (sender == self.allowLocalCheckbox) { - [settings setValue: @(isChecked) forKey: @"AllowLocalNetworks"]; - } else if (sender == self.includeSubdomainsCheckbox) { - [settings setValue: @(isChecked) forKey: @"EvaluateCommonSubdomains"]; - } else if (sender == self.includeLinkedSitesCheckbox) { - [settings setValue: @(isChecked) forKey: @"IncludeLinkedDomains"]; - } -} - -- (void)viewDidLoad { - [self refreshFromSecuredSettings]; -} -- (void)viewDidAppear { - [self refreshFromSecuredSettings]; -} - - #pragma mark MASPreferencesViewController - (NSString*)identifier { diff --git a/PreferencesGeneralViewController.h b/PreferencesGeneralViewController.h index 3246362b..a1ba88a3 100644 --- a/PreferencesGeneralViewController.h +++ b/PreferencesGeneralViewController.h @@ -11,7 +11,6 @@ @interface PreferencesGeneralViewController : NSViewController -@property IBOutlet NSButton* playSoundCheckbox; @property IBOutlet NSPopUpButton* soundMenu; - (IBAction)soundSelectionChanged:(id)sender; diff --git a/PreferencesGeneralViewController.m b/PreferencesGeneralViewController.m index d67e4c91..22b49bdd 100644 --- a/PreferencesGeneralViewController.m +++ b/PreferencesGeneralViewController.m @@ -8,7 +8,6 @@ #import "PreferencesGeneralViewController.h" #import "SCSettings.h" -#import "SCConstants.h" @interface PreferencesGeneralViewController () @@ -20,68 +19,38 @@ - (instancetype)init { return [super initWithNibName: @"PreferencesGeneralViewController" bundle: nil]; } -- (void)refreshBlockSoundFromSettings { - SCSettings* settings = [SCSettings currentUserSettings]; - BOOL blockSoundShouldPlay = [[settings valueForKey: @"BlockSoundShouldPlay"] boolValue]; - NSInteger blockSoundIndex = [[settings valueForKey: @"BlockSound"] integerValue]; - - self.playSoundCheckbox.state = blockSoundShouldPlay; - [self.soundMenu selectItemAtIndex: blockSoundIndex]; - self.soundMenu.enabled = blockSoundShouldPlay; -} - - (void)viewDidLoad { // set the valid sounds in the Block Sound menu [self.soundMenu removeAllItems]; - [self.soundMenu addItemsWithTitles: [SCConstants systemSoundNames]]; - - [self refreshBlockSoundFromSettings]; -} -- (void)viewDidAppear { - [self refreshBlockSoundFromSettings]; + [self.soundMenu addItemsWithTitles: SCConstants.systemSoundNames]; } - (IBAction)soundSelectionChanged:(NSPopUpButton*)sender { // Map the tags used in interface builder to the sound - NSArray* systemSoundNames = [SCConstants systemSoundNames]; + NSArray* systemSoundNames = SCConstants.systemSoundNames; NSString* selectedSoundName = sender.titleOfSelectedItem; NSInteger blockSoundIndex = [systemSoundNames indexOfObject: selectedSoundName]; if (blockSoundIndex == NSNotFound) { NSLog(@"WARNING: User selected unknown alert sound %@.", selectedSoundName); - NSError* err = [NSError errorWithDomain: @"SelfControlErrorDomain" - code: -902 - userInfo: @{NSLocalizedDescriptionKey: @"Error -902: Unknown sound selected."}]; + NSError* err = [SCErr errorWithCode: 310]; + [SCSentry captureError: err]; [NSApp presentError: err]; return; } - [[SCSettings currentUserSettings] setValue: @(blockSoundIndex) forKey: @"BlockSound"]; // now play the sound to preview it for the user NSSound* alertSound = [NSSound soundNamed: systemSoundNames[blockSoundIndex]]; if(!alertSound) { NSLog(@"WARNING: Alert sound not found."); - NSError* err = [NSError errorWithDomain: @"SelfControlErrorDomain" - code: -901 - userInfo: @{NSLocalizedDescriptionKey: @"Error -901: Selected sound not found."}]; + NSError* err = [SCErr errorWithCode: 311]; + [SCSentry captureError: err]; [NSApp presentError: err]; } else { [alertSound play]; } } -- (IBAction)soundCheckboxChanged:(NSButton*)sender { - BOOL isChecked = (((NSButton*)sender).state == NSOnState); - SCSettings* settings = [SCSettings currentUserSettings]; - - if (sender == self.playSoundCheckbox) { - [settings setValue: @(isChecked) forKey: @"BlockSoundShouldPlay"]; - } - - // enable the sound menu only if sound playback is enabled - self.soundMenu.enabled = isChecked; -} - #pragma mark MASPreferencesViewController - (NSString*)identifier { diff --git a/SCConstants.h b/SCConstants.h index 47c1027d..3758d999 100644 --- a/SCConstants.h +++ b/SCConstants.h @@ -9,9 +9,12 @@ NS_ASSUME_NONNULL_BEGIN +extern OSStatus const AUTH_CANCELLED_STATUS; + @interface SCConstants : NSObject -+ (NSArray*) systemSoundNames; +@property (class, readonly, nonatomic) NSArray* systemSoundNames; +@property (class, readonly, nonatomic) NSDictionary* const defaultUserDefaults; @end diff --git a/SCConstants.m b/SCConstants.m index f9d1f09c..ac737b2b 100644 --- a/SCConstants.m +++ b/SCConstants.m @@ -7,23 +7,64 @@ #import "SCConstants.h" +OSStatus const AUTH_CANCELLED_STATUS = -60006; + @implementation SCConstants + (NSArray*)systemSoundNames { - return @[@"Basso", - @"Blow", - @"Bottle", - @"Frog", - @"Funk", - @"Glass", - @"Hero", - @"Morse", - @"Ping", - @"Pop", - @"Purr", - @"Sosumi", - @"Submarine", - @"Tink"]; + static NSArray* soundsArr = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + soundsArr = @[@"Basso", + @"Blow", + @"Bottle", + @"Frog", + @"Funk", + @"Glass", + @"Hero", + @"Morse", + @"Ping", + @"Pop", + @"Purr", + @"Sosumi", + @"Submarine", + @"Tink"]; + }); + + return soundsArr; +} + ++ (NSDictionary*)defaultUserDefaults { + static NSDictionary* defaultDefaultsDict = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaultDefaultsDict = @{ + @"Blocklist": @[], + @"BlockAsWhitelist": @NO, + @"HighlightInvalidHosts": @YES, + @"VerifyInternetConnection": @YES, + @"TimerWindowFloats": @NO, + @"BadgeApplicationIcon": @YES, + @"MaxBlockLength": @1440, + @"BlockLengthInterval": @15, + @"WhitelistAlertSuppress": @NO, + @"GetStartedShown": @NO, + @"EvaluateCommonSubdomains": @YES, + @"IncludeLinkedDomains": @YES, + @"BlockSoundShouldPlay": @NO, + @"BlockSound": @5, + @"ClearCaches": @YES, + @"AllowLocalNetworks": @YES, + @"EnableErrorReporting": @NO, + @"ErrorReportingPromptDismissed": @NO, + + @"V4MigrationComplete": @NO + }; + }); + + return defaultDefaultsDict; } @end diff --git a/SCError.strings b/SCError.strings new file mode 100644 index 00000000..48fcc012 --- /dev/null +++ b/SCError.strings @@ -0,0 +1,50 @@ +/* + SCError.strings + SelfControl + + Created by Charlie Stigler on 1/13/21. + +*/ + +// 1-99 = general / generic errors +"1" = "Authorization cancelled"; + +// 100 - 199 = errors generated in the app +"100" = "You can't start block, because the blocklist is empty."; +"101" = "There was an error saving the blocklist to a file: %@"; +"102" = "You can't add to the blocklist, because no block is currently running."; +"103" = "You can't extend block duration, because no block is currently running."; +"104" = "You can't start a block, because another block is currently running."; +"105" = "The block wasn't removed at the scheduled time, for unknown reasons."; +"106" = "Data couldn't be written to that location."; + +// 200 - 299 = errors generated in the CLI + +// 300-399 = errors generated in the daemon +"300" = "Command failed because of a timeout acquiring the request lock."; +"301" = "SelfControl couldn't start a block because a block is already running."; +"302" = "SelfControl couldn't start a block because the blocklist is empty, or the block end date is in the past."; +"303" = "SelfControl couldn't update the blocklist because the current block was started with an older version of SelfControl."; +"304" = "SelfControl couldn't add to the blocklist because no block is currently running."; +"305" = "SelfControl won't add to the blocklist because the current block uses an allowlist."; +"306" = "SelfControl could't extend the block duration bcecause the current block was started with an older version of SelfControl."; +"307" = "SelfControl couldn't extend the block duration because no block is currently running."; +"308" = "SelfControl won't update the block to end at an earlier date - it can only be extended."; +"309" = "SelfControl won't extend the block by more than 24 hours at a time."; +"310" = "There was an error switching alert sounds because that sound name is unknown."; +"310" = "There was an error switching alert sounds because the system couldn't find that sound."; + +// 400-499 = errors generated in the killer +"400" = "SelfControl couldn't manually clear the block, because there was an error running the helper tool."; + +// 500-599 = errors generated in the XPC client +"500" = "There was an error trying to install SelfControl's helper tool: %@"; +"501" = "There was an error authorizing the installation of SelfControl's helper tool."; + +// 600-699 = errors generated in SCSettings +"600" = "Unable to write SCSettings from a normal (non-root) account."; + +// 700-799 = errors generated in SelfControl utility functions +"700" = "SelfControl couldn't clear your browser caches, because it couldn't read the user home directories."; +"701" = "SelfControl couldn't clear out your settings from old version of SelfControl, because we had insufficient permissions."; +"702" = "SelfControl couldn't clear out your settings from old version of SelfControl, because a block from an older version is ongoing."; diff --git a/SCKillerHelper/main.m b/SCKillerHelper/main.m index 204a17e5..fe9039fa 100644 --- a/SCKillerHelper/main.m +++ b/SCKillerHelper/main.m @@ -11,12 +11,18 @@ #import #import "BlockManager.h" #import "SCSettings.h" -#import "HelperCommon.h" +#import "SCHelperToolUtilities.h" +#import +#import "SCMigrationUtilities.h" +#import #define LOG_FILE @"~/Documents/SelfControl-Killer.log" int main(int argc, char* argv[]) { @autoreleasepool { + // make sure to expand the tilde before we setuid to 0, otherwise this won't work + NSString* logFilePath = [LOG_FILE stringByExpandingTildeInPath]; + NSMutableString* log = [NSMutableString stringWithString: @"===SelfControl-Killer Log File===\n\n"]; if(geteuid()) { NSLog(@"ERROR: Helper tool must be run as root."); @@ -29,19 +35,20 @@ int main(int argc, char* argv[]) { } int controllingUID = [@(argv[1]) intValue]; - NSString* logFilePath = [LOG_FILE stringByExpandingTildeInPath]; - - NSMutableString* log = [NSMutableString stringWithString: @"===SelfControl-Killer Log File===\n\n"]; + // we need to setuid to root, otherwise launchctl won't find system launch daemons + // depite the EUID being 0 as expected - not sure why that is + setuid(0); /* FIRST TASK: print debug info */ // print SC version: NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary]; NSString* version = [infoDict objectForKey:@"CFBundleShortVersionString"]; - [log appendFormat: @"SelfControl Version: %@\n", version]; + NSString* buildNumber = [infoDict objectForKey: @"CFBundleVersion"]; + [log appendFormat: @"SelfControl Version: %@ (%@)\n", version, buildNumber]; // print system version - [log appendFormat: @"System Version: Mac OS X %@", [[NSProcessInfo processInfo] operatingSystemVersionString]]; + [log appendFormat: @"System Version: Mac OS X %@\n\n", [[NSProcessInfo processInfo] operatingSystemVersionString]]; // print launchd daemons int status; @@ -79,13 +86,21 @@ int main(int argc, char* argv[]) { [task waitUntilExit]; status = [task terminationStatus]; if(defaultsList) { - [log appendFormat: @"Current user defaults:\n\n%@\n", defaultsList]; + [log appendFormat: @"Current user (%u) defaults:\n\n%@\n", getuid(), defaultsList]; } seteuid(0); // and print new secured settings, if they exist - SCSettings* settings = [SCSettings settingsForUser: controllingUID]; + SCSettings* settings = [SCSettings sharedSettings]; [log appendFormat: @"Current secured settings:\n\n:%@\n", settings.dictionaryRepresentation]; + + NSString* legacySettingsPath = [SCMigrationUtilities legacySecuredSettingsFilePathForUser: controllingUID]; + NSDictionary* legacySettingsDict = [NSDictionary dictionaryWithContentsOfFile: legacySettingsPath]; + if (legacySettingsDict) { + [log appendFormat: @"Legacy (3.0-3.0.3) secured settings:\n\n:%@\n", legacySettingsDict]; + } else { + [log appendFormat: @"Couldn't find legacy settings (from v3.0-3.0.3).\n\n"]; + } NSFileManager* fileManager = [NSFileManager defaultManager]; @@ -101,7 +116,7 @@ int main(int argc, char* argv[]) { if([mainConf length]) { [log appendFormat: @"pf.conf file contents:\n\n%@\n", mainConf]; } else { - [log appendString: @"Could not find pf.conf file.\n"]; + [log appendString: @"Could not find pf.conf file.\n\n"]; } // print org.eyebeam pf anchors @@ -119,9 +134,18 @@ int main(int argc, char* argv[]) { @"-w", @"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]]; [task waitUntilExit]; - - status = [task terminationStatus]; - [log appendFormat: @"Unloading the launchd daemon returned: %d\n\n", status]; + status = [task terminationStatus]; + [log appendFormat: @"Unloading the legacy (1.0 - 3.0.3) launchd daemon returned: %d\n\n", status]; + + CFErrorRef cfError; + SILENCE_OSX10_10_DEPRECATION( + SMJobRemove(kSMDomainSystemLaunchd, CFSTR("org.eyebeam.selfcontrold"), NULL, YES, &cfError); + ); + if (cfError) { + [log appendFormat: @"Failed to remove selfcontrold daemon (4.x) with error %@\n\n", cfError]; + } else { + [log appendFormat: @"Successfully removed selfcontrold daemon (4.x)!\n\n"]; + } BlockManager* bm = [[BlockManager alloc] init]; BOOL cleared = [bm forceClearBlock]; @@ -142,12 +166,19 @@ int main(int argc, char* argv[]) { [log appendFormat: @"Deleting BlockStartedDate from defaults returned: %d\n", status]; seteuid(0); - // clear BlockEndDate (new date value) from secured settings - [settings setValue: nil forKey: @"BlockEndDate"]; - [settings setValue: nil forKey: @"BlockIsRunning"]; + // clear all modern secured settings (the user's copies of each setting will still be stored in defaults) + [settings resetAllSettingsToDefaults]; [settings synchronizeSettings]; - [log appendFormat: @"Deleted BlockEndDate and BlockIsRunning from secured settings\n"]; + [log appendFormat: @"Reset all modern secured settings to default values.\n"]; + if ([SCMigrationUtilities legacySettingsFoundForUser: controllingUID]) { + [SCMigrationUtilities copyLegacySettingsToDefaults: controllingUID]; + [SCMigrationUtilities clearLegacySettingsForUser: controllingUID]; + [log appendFormat: @"Found, copied, and cleared legacy settings (v3.0-3.0.3)!\n"]; + } else { + [log appendFormat: @"No legacy settings (v3.0-3.0.3) found.\n"]; + } + // remove PF token if([fileManager removeItemAtPath: @"/etc/SelfControlPFToken" error: nil]) { [log appendString: @"\nRemoved PF token file successfully.\n"]; @@ -191,40 +222,25 @@ int main(int argc, char* argv[]) { } } } - - // Now that the current block is over, we can go ahead and remove the legacy block info - // and migrate them to the new SCSettings system - [settings clearLegacySettings]; - [log appendString: @"\nMigrating settings to new system...\n"]; - + // OK, make sure all settings are synced before this thing exits - [settings synchronizeSettingsWithCompletion:^(NSError* err) { - if (err != nil) { - [log appendFormat: @"\nWARNING: Settings failed to synchronize before exit, with error %@", err]; - } + NSError* syncSettingsErr = [settings syncSettingsAndWait: 5]; + + if (syncSettingsErr != nil) { + [log appendFormat: @"\nWARNING: Settings failed to synchronize before exit, with error %@", syncSettingsErr]; + } - // let the main app know to refresh - sendConfigurationChangedNotification(); + [log appendString: @"\n===SelfControl-Killer complete!==="]; - [log appendString: @"\n===SelfControl-Killer complete!==="]; + [log writeToFile: logFilePath + atomically: YES + encoding: NSUTF8StringEncoding + error: nil]; - [log writeToFile: logFilePath - atomically: YES - encoding: NSUTF8StringEncoding - error: nil]; - exit(EX_OK); - }]; - - // only wait 5 seconds for the sync to finish, otherwise exit anyway - sleep(5); - - [log appendString: @"\nWARNING: Settings timed out synchronizing before exit"]; - [log writeToFile: logFilePath - atomically: YES - encoding: NSUTF8StringEncoding - error: nil]; - + // let the main app know to refresh + [SCHelperToolUtilities sendConfigurationChangedNotification]; + exit(EX_OK); } } diff --git a/SCUtilities.h b/SCUtilities.h deleted file mode 100644 index 4bbd3f9e..00000000 --- a/SCUtilities.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// SCBlockDateUtilities.h -// SelfControl -// -// Created by Charles Stigler on 07/07/2018. -// - -#import - -@class SCSettings; - -// Holds utility methods for use throughout SelfControl - - -@interface SCUtilities : NSObject - -+ (NSArray*) cleanBlocklistEntry:(NSString*)rawEntry; - -/* BLOCK SETTING METHODS */ - -// The point of these methods is basically to abstract out whether we're using blockStartedDate (the old system) -// or blockEndDate (the new system) for tracking blocks in settings/lockfile. We want to be backwards-compatible for a while so people -// who upgrade mid-block (foolishly!) have a better chance of surviving and we don't bork their stuff. -// eventually, another way to do this would just be to convert all blockStartedDates to blockEndDates on launch, -// but that sounds risky (updating lock files is not guaranteed) and this seems safer for now... - -// Main app functions taking NSUserDefaults and SCSettings - -+ (void) startBlockInSettings:(SCSettings*)settings withBlockDuration:(NSTimeInterval)blockDuration; -+ (void) removeBlockFromSettings:(SCSettings*)settings; -+ (void) removeBlockFromSettingsForUID:(uid_t)uid; - -// Helper tool functions dealing with dictionaries and setDefaultsValue helper - -// uses the below methods as well as filesystem checks to see if the block is REALLY running or not -+ (BOOL) blockIsRunningWithSettings:(SCSettings*)settings defaults:(NSUserDefaults*)defaults; - -+ (BOOL) blockIsRunningInDictionary:(NSDictionary*)dict; -+ (BOOL) blockShouldBeRunningInDictionary:(NSDictionary *)dict; - -+ (BOOL) blockIsRunningInLegacyDictionary:(NSDictionary*)dict; -+ (NSDate*) endDateFromLegacyBlockDictionary:(NSDictionary *)dict; - -// read and write saved block files -+ (BOOL)writeBlocklistToFileURL:(NSURL*)targetFileURL settings:(SCSettings*)settings errorDescription:(NSString**)errDescriptionRef; -+ (BOOL)readBlocklistFromFile:(NSURL*)fileURL toSettings:(SCSettings*)settings; - -@end diff --git a/SCUtilities.m b/SCUtilities.m deleted file mode 100644 index 22e26839..00000000 --- a/SCUtilities.m +++ /dev/null @@ -1,240 +0,0 @@ -// -// SCBlockDateUtilities.m -// SelfControl -// -// Created by Charles Stigler on 07/07/2018. -// - -#import "SCUtilities.h" -#import "HelperCommon.h" -#import "SCSettings.h" - -@implementation SCUtilities - -// Standardize and clean up the input value so it'll block properly (and look good doing it) -// note that if the user entered line breaks, we'll split it into many entries, so this can return multiple -// cleaned entries in the NSArray it returns -+ (NSArray*) cleanBlocklistEntry:(NSString*)rawEntry { - if (rawEntry == nil) return @[]; - - // This'll remove whitespace and lowercase the string. - NSString* str = [[rawEntry stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString]; - - // if there are newlines in the string, split it and process it as many strings - if([str rangeOfCharacterFromSet: [NSCharacterSet newlineCharacterSet]].location != NSNotFound) { - NSArray* splitEntries = [str componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]; - - NSMutableArray* returnArr = [NSMutableArray new]; - for (NSString* splitEntry in splitEntries) { - // recursion makes the rest of the code prettier - NSArray* cleanedSubEntries = [SCUtilities cleanBlocklistEntry: splitEntry]; - [returnArr addObjectsFromArray: cleanedSubEntries]; - } - return returnArr; - } - - // if the user entered a scheme (https://, http://, etc) remove it. - // We only block hostnames so scheme is ignored anyway and it can gunk up the blocking - NSArray* separatedStr = [str componentsSeparatedByString: @"://"]; - str = [separatedStr lastObject]; - - // Remove URL login names/passwords (username:password@host) if a user tried to put that in - separatedStr = [str componentsSeparatedByString: @"@"]; - str = [separatedStr lastObject]; - - // now here's where it gets tricky. Besides just hostnames, we also support CIDR IP ranges, for example: 83.0.1.2/24 - // so we are gonna keep track of whether we might have received a valid CIDR IP range instead of hostname as we go... - // we also take port numbers, so keep track of whether we have one of those - int cidrMaskBits = -1; - int portNum = -1; - - // first pull off everything after a slash - // discard the end if it's just a path, but check to see if it might be our CIDR mask length - separatedStr = [str componentsSeparatedByString: @"/"]; - str = [separatedStr firstObject]; - - // if the part after a slash is an integer between 1 and 128, it could be our mask length - if (separatedStr.count > 1) { - int potentialMaskLen = [[separatedStr lastObject] intValue]; - if (potentialMaskLen > 0 && potentialMaskLen <= 128) cidrMaskBits = potentialMaskLen; - } - - // check for the port - separatedStr = [str componentsSeparatedByString: @":"]; - str = [separatedStr firstObject]; - - if (separatedStr.count > 1) { - int potentialPort = [[separatedStr lastObject] intValue]; - if (potentialPort > 0 && potentialPort <= 65535) { - portNum = potentialPort; - } - } - - // remove invalid characters from the hostname - // hostnames are 1-253 characters long, and can contain only a-z, A-Z, 0-9, -, and ., and maybe _ (mostly not but kinda) - // for some reason [NSCharacterSet URLHostAllowedCharacterSet] has tons of other characters that aren't actually valid - NSMutableCharacterSet* invalidHostnameChars = [[NSCharacterSet alphanumericCharacterSet] mutableCopy]; - [invalidHostnameChars addCharactersInString: @"-._"]; - [invalidHostnameChars invert]; - - NSMutableString* validCharsOnly = [NSMutableString stringWithCapacity: str.length]; - for (NSUInteger i = 0; i < str.length && i < 253; i++) { - unichar c = [str characterAtIndex: i]; - if (![invalidHostnameChars characterIsMember: c]) { - [validCharsOnly appendFormat: @"%C", c]; - } - } - str = validCharsOnly; - - // allow blocking an empty hostname IFF we're only blocking a single port number (i.e. :80) - // otherwise, empty hostname = nothing to do - if (str.length < 1 && portNum < 0) { - return @[]; - } - - NSString* maskString; - NSString* portString; - - // create a mask string if we have one - if (cidrMaskBits < 0) { - maskString = @""; - } else { - maskString = [NSString stringWithFormat: @"/%d", cidrMaskBits]; - } - - // create a port string if we have one - if (portNum < 0) { - portString = @""; - } else { - portString = [NSString stringWithFormat: @":%d", portNum]; - } - - // combine em together and you got something! - return @[[NSString stringWithFormat: @"%@%@%@", str, maskString, portString]]; -} - - -+ (BOOL) blockIsRunningWithSettings:(SCSettings*)settings defaults:(NSUserDefaults*)defaults { - // first we look for the answer in the SCSettings system - if ([SCUtilities blockIsRunningInDictionary: settings.dictionaryRepresentation]) { - return YES; - } - - // next we check the host file, and see if a block is in there - NSString* hostFileContents = [NSString stringWithContentsOfFile: @"/etc/hosts" encoding: NSUTF8StringEncoding error: NULL]; - if(hostFileContents != nil && [hostFileContents rangeOfString: @"# BEGIN SELFCONTROL BLOCK"].location != NSNotFound) { - return YES; - } - - // finally, we should check the legacy ways of storing a block (defaults and lockfile) - - [defaults synchronize]; - if ([SCUtilities blockIsRunningInDictionary: defaults.dictionaryRepresentation]) { - return YES; - } - - // If there's no block in the hosts file, SCSettings block in the defaults, and no lock-file, - // we'll assume we're clear of blocks. Checking pf would be nice but usually requires - // root permissions, so it would be difficult to do here. - return [[NSFileManager defaultManager] fileExistsAtPath: SelfControlLegacyLockFilePath]; -} - -// returns YES if a block is actively running (to the best of our knowledge), and NO otherwise -+ (BOOL) blockIsRunningInDictionary:(NSDictionary *)dict { - // simple: the block is running if BlockIsRunning is set to true! - return [[dict valueForKey: @"BlockIsRunning"] boolValue]; -} - -// returns YES if the block should be active based on the specified end time (i.e. it is in the future), or NO otherwise -+ (BOOL) blockShouldBeRunningInDictionary:(NSDictionary *)dict { - // the block should be running if the end date hasn't arrived yet - if ([[dict objectForKey: @"BlockEndDate"] timeIntervalSinceNow] > 0) { - return YES; - } else { - return NO; - } -} - -+ (void) startBlockInSettings:(SCSettings*)settings withBlockDuration:(NSTimeInterval)blockDuration { - // sanity check duration (must be above zero) - blockDuration = MAX(blockDuration, 0); - - // assume the block is starting now - NSDate* blockEndDate = [NSDate dateWithTimeIntervalSinceNow: blockDuration]; - - [settings setValue: blockEndDate forKey: @"BlockEndDate"]; -} - - -+ (void) removeBlockFromSettings:(SCSettings*)settings { - // TODO: will this work setting nil instead of [NSDate dateWithTimeIntervalSince1970: 0]? - [settings setValue: nil forKey: @"BlockEndDate"]; - [settings setValue: nil forKey: @"BlockIsRunning"]; -} - -+ (void) removeBlockFromSettingsForUID:(uid_t)uid { - SCSettings* settings = [SCSettings settingsForUser: uid]; - [SCUtilities removeBlockFromSettings: settings]; -} - -+ (BOOL)blockIsRunningInLegacyDictionary:(NSDictionary*)dict { - NSDate* blockStartedDate = [dict objectForKey:@"BlockStartedDate"]; - - // the block is running if BlockStartedDate exists and isn't equal to the default value - if (blockStartedDate != nil && ![blockStartedDate isEqualToDate: [NSDate distantFuture]]) { - return YES; - } else { - return NO; - } -} -+ (NSDate*) endDateFromLegacyBlockDictionary:(NSDictionary *)dict { - NSDate* startDate = [dict objectForKey: @"BlockStartedDate"]; - NSTimeInterval duration = [[dict objectForKey: @"BlockDuration"] floatValue]; - - // if we don't have a start date in the past and a duration greater than 0, we don't have a block end date - if (startDate == nil || [startDate timeIntervalSinceNow] >= 0 || duration <= 0) { - return [NSDate distantPast]; - } - - // convert the legacy start date to an end date - return [startDate dateByAddingTimeInterval: (duration * 60)]; -} - -+ (BOOL)writeBlocklistToFileURL:(NSURL*)targetFileURL settings:(SCSettings*)settings errorDescription:(NSString**)errDescriptionRef { - NSDictionary* saveDict = @{@"HostBlacklist": [settings valueForKey: @"Blocklist"], - @"BlockAsWhitelist": [settings valueForKey: @"BlockAsWhitelist"]}; - - NSString* saveDataErr; - NSData* saveData = [NSPropertyListSerialization dataFromPropertyList: saveDict format: NSPropertyListBinaryFormat_v1_0 errorDescription: &saveDataErr]; - if (saveDataErr != nil) { - *errDescriptionRef = saveDataErr; - return NO; - } - - if (![saveData writeToURL: targetFileURL atomically: YES]) { - NSLog(@"ERROR: Failed to write blocklist to URL %@", targetFileURL); - return NO; - } - - // for prettiness sake, attempt to hide the file extension - NSDictionary* attribs = @{NSFileExtensionHidden: @YES}; - [[NSFileManager defaultManager] setAttributes: attribs ofItemAtPath: [targetFileURL path] error: NULL]; - - return YES; -} - -+ (BOOL)readBlocklistFromFile:(NSURL*)fileURL toSettings:(SCSettings*)settings { - NSDictionary* openedDict = [NSDictionary dictionaryWithContentsOfURL: fileURL]; - - if (openedDict == nil || openedDict[@"HostBlacklist"] == nil || openedDict[@"BlockAsWhitelist"] == nil) { - NSLog(@"ERROR: Could not read a valid block from file %@", fileURL); - return NO; - } - - [settings setValue: openedDict[@"HostBlacklist"] forKey: @"Blocklist"]; - [settings setValue: openedDict[@"BlockAsWhitelist"] forKey: @"BlockAsWhitelist"]; - - return YES; -} - -@end diff --git a/SelfControl Killer/AppDelegate.m b/SelfControl Killer/AppDelegate.m index 31724f65..f37f553a 100644 --- a/SelfControl Killer/AppDelegate.m +++ b/SelfControl Killer/AppDelegate.m @@ -47,12 +47,16 @@ - (IBAction)killButtonClicked:(id)sender { &authorizationRef); if(status) { - NSLog(@"ERROR: Failed to authorize block kill."); - return; + // if it's just the user cancelling, make that obvious + // to any listeners so they can ignore it appropriately + if (status != AUTH_CANCELLED_STATUS) { + NSLog(@"ERROR: Failed to authorize block kill with status %d.", status); + } + return; } // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid - [[SCSettings currentUserSettings] synchronizeSettings]; + [[SCSettings sharedSettings] synchronizeSettings]; char uidString[10]; snprintf(uidString, sizeof(uidString), "%d", getuid()); @@ -67,9 +71,13 @@ - (IBAction)killButtonClicked:(id)sender { if(status) { NSLog(@"WARNING: Authorized execution of helper tool returned failure status code %d", status); - NSError* err = [NSError errorWithDomain: @"org.eyebeam.SelfControl-Killer" code: status userInfo: @{NSLocalizedDescriptionKey: @"Error executing privileged helper tool."}]; + /// AUTH_CANCELLED_STATUS just means auth is cancelled, not really an "error" per se + if (status != AUTH_CANCELLED_STATUS) { + NSError* err = [SCErr errorWithCode: 400]; + [SCSentry captureError: err]; - [NSApp presentError: err]; + [NSApp presentError: err]; + } return; } else { @@ -93,10 +101,11 @@ - (NSString*)selfControlKillerHelperToolPath { static NSString* path; // Cache the path so it doesn't have to be searched for again. - if(!path) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ NSBundle* thisBundle = [NSBundle mainBundle]; path = [thisBundle pathForAuxiliaryExecutable: @"SCKillerHelper"]; - } + }); return path; } diff --git a/SelfControl Killer/Info.plist b/SelfControl Killer/Info.plist index 5ea7d07d..e33d4691 100644 --- a/SelfControl Killer/Info.plist +++ b/SelfControl Killer/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.0.3 + 4.0 alpha 1 CFBundleSignature ???? CFBundleVersion - $(CURRENT_PROJECT_VERSION) + 399 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/SelfControl.xcodeproj/project.pbxproj b/SelfControl.xcodeproj/project.pbxproj index 5fea1715..16c531e6 100644 --- a/SelfControl.xcodeproj/project.pbxproj +++ b/SelfControl.xcodeproj/project.pbxproj @@ -7,17 +7,31 @@ objects = { /* Begin PBXBuildFile section */ + 01EFCB03830117674B4E6B21 /* libPods-org.eyebeam.selfcontrold.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6494AB6FC557A9B46A222422 /* libPods-org.eyebeam.selfcontrold.a */; }; + 051230B633C4B37DD5CF305E /* libPods-selfcontrol-cli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F946636EDF2639CE7629BF6C /* libPods-selfcontrol-cli.a */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; - AA0270C7A1D37351F38296D7 /* libPods-SelfControl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 59B433289D7F3EC8A3F9632F /* libPods-SelfControl.a */; }; + 9A31F3D484C646257DB308C5 /* Pods_SelfControl_Killer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BA749B3BC24DC33425F27DF /* Pods_SelfControl_Killer.framework */; }; CB0385E119D77051004614B6 /* PreferencesAdvancedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB0385DD19D77051004614B6 /* PreferencesAdvancedViewController.xib */; }; CB0385E219D77051004614B6 /* PreferencesGeneralViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB0385DF19D77051004614B6 /* PreferencesGeneralViewController.xib */; }; - CB0EEF7820FE49030024D27B /* SCUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB0EEF7720FE49020024D27B /* SCUtilitiesTests.m */; }; + CB0EEF7820FE49030024D27B /* SCUtilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB0EEF7720FE49020024D27B /* SCUtilityTests.m */; }; CB114283222CCF19004B7868 /* SCSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF3B573217BADD7006D5F52 /* SCSettings.m */; }; CB114284222CD4F0004B7868 /* SCSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF3B573217BADD7006D5F52 /* SCSettings.m */; }; + CB1465B825B027E700130D2E /* SCErr.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1465B725B027E700130D2E /* SCErr.m */; }; + CB1465B925B027E700130D2E /* SCErr.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1465B725B027E700130D2E /* SCErr.m */; }; + CB1465BA25B027E700130D2E /* SCErr.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1465B725B027E700130D2E /* SCErr.m */; }; + CB1465BB25B027E700130D2E /* SCErr.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1465B725B027E700130D2E /* SCErr.m */; }; + CB1465BC25B027E700130D2E /* SCErr.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1465B725B027E700130D2E /* SCErr.m */; }; + CB1465C425B0285300130D2E /* SCError.strings in Resources */ = {isa = PBXBuildFile; fileRef = CB1465C325B0285300130D2E /* SCError.strings */; }; + CB1465C525B0285300130D2E /* SCError.strings in Resources */ = {isa = PBXBuildFile; fileRef = CB1465C325B0285300130D2E /* SCError.strings */; }; CB1B44BE23B7F1ED00EBA087 /* cheater-background.png in Resources */ = {isa = PBXBuildFile; fileRef = CB1B44BD23B7F1ED00EBA087 /* cheater-background.png */; }; - CB1E7A6E0F9AEA9B00D158BC /* ERRORS in Resources */ = {isa = PBXBuildFile; fileRef = CB1E7A6D0F9AEA9B00D158BC /* ERRORS */; }; - CB1ED4480F56686400EFECEE /* org.eyebeam.SelfControl in Copy Helper Tools */ = {isa = PBXBuildFile; fileRef = CBA2AFD20F39EC12005AFEBE /* org.eyebeam.SelfControl */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + CB1CA64D25ABA5BB0084A551 /* SCXPCAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = CB69C4ED25A3FD8A0030CFCD /* SCXPCAuthorization.m */; }; + CB1CA64E25ABA5BB0084A551 /* SCXPCAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = CB69C4ED25A3FD8A0030CFCD /* SCXPCAuthorization.m */; }; + CB1CA64F25ABA5BB0084A551 /* SCXPCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */; }; + CB1CA65025ABA5BB0084A551 /* SCXPCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */; }; + CB1CA65125ABA5BB0084A551 /* SCXPCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */; }; + CB1CA65E25ABA6150084A551 /* SCXPCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */; }; + CB1CA66525ABA6240084A551 /* SCXPCAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = CB69C4ED25A3FD8A0030CFCD /* SCXPCAuthorization.m */; }; CB20C5D8245699D700B9D749 /* version-header.h in Sources */ = {isa = PBXBuildFile; fileRef = CB20C5D7245699D700B9D749 /* version-header.h */; }; CB249FED19D782230087BBB6 /* SelfControlIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = CB249FEC19D782230087BBB6 /* SelfControlIcon.icns */; }; CB25806216C1FDBE0059C99A /* BlockManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CB25806116C1FDBE0059C99A /* BlockManager.m */; }; @@ -34,13 +48,70 @@ CB529BBF0F32B7ED00564FB8 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = CB529BBE0F32B7ED00564FB8 /* AppController.m */; }; CB54D44C0F93E33300AA22E9 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9E901D0F397FFA006DE6E4 /* Security.framework */; }; CB587E500F50FE8800C66A09 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB587E4F0F50FE8800C66A09 /* SystemConfiguration.framework */; }; + CB58948025B3FC6D00E9A5C0 /* HostFileBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB0AE290FA74566006229B3 /* HostFileBlocker.m */; }; + CB58948725B3FC6F00E9A5C0 /* HostFileBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB0AE290FA74566006229B3 /* HostFileBlocker.m */; }; CB5DFCB72251DD1F0084CEC2 /* SCConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */; }; CB5DFCB82251DD1F0084CEC2 /* SCConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */; }; CB5DFCBA2251DD1F0084CEC2 /* SCConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */; }; CB5DFCBB2251DD1F0084CEC2 /* SCConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */; }; CB5DFCBC2251DD1F0084CEC2 /* SCConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */; }; + CB62FC3024B11A4F00ADBC40 /* selfcontrold-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = CB62FC2F24B11A4F00ADBC40 /* selfcontrold-Info.plist */; }; + CB62FC3E24B1298500ADBC40 /* SCDaemonBlockMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = CB62FC3D24B1298500ADBC40 /* SCDaemonBlockMethods.m */; }; + CB62FC3F24B1327A00ADBC40 /* SCMiscUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */; }; + CB62FC4024B1327D00ADBC40 /* SCSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF3B573217BADD7006D5F52 /* SCSettings.m */; }; + CB62FC4224B1329200ADBC40 /* BlockManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CB25806116C1FDBE0059C99A /* BlockManager.m */; }; + CB62FC4324B1329500ADBC40 /* PacketFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCA91111960D87300AFD20C /* PacketFilter.m */; }; + CB62FC4424B1329800ADBC40 /* HostFileBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB0AE290FA74566006229B3 /* HostFileBlocker.m */; }; + CB62FC4524B1329F00ADBC40 /* ThunderbirdPreferenceParser.m in Sources */ = {isa = PBXBuildFile; fileRef = CBE4401A0F4BE0670062A1FE /* ThunderbirdPreferenceParser.m */; }; + CB62FC4624B132A300ADBC40 /* HostImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB90BF820F49F430006D202D /* HostImporter.m */; }; + CB62FC4724B132A600ADBC40 /* AllowlistScraper.m in Sources */ = {isa = PBXBuildFile; fileRef = CB73615F19E4FDA000E0924F /* AllowlistScraper.m */; }; + CB62FC4824B132B300ADBC40 /* SCConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */; }; + CB62FC4924B1330700ADBC40 /* LaunchctlHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC2F8570F4672FE00CF2A42 /* LaunchctlHelper.m */; }; CB6840A5247A3E5500E51564 /* SelfControlKillerIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = CB6840A4247A3E5500E51564 /* SelfControlKillerIcon.icns */; }; + CB69C4EF25A3FD8A0030CFCD /* SCXPCAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = CB69C4ED25A3FD8A0030CFCD /* SCXPCAuthorization.m */; }; CB73616219E5086A00E0924F /* AllowlistScraper.m in Sources */ = {isa = PBXBuildFile; fileRef = CB73615F19E4FDA000E0924F /* AllowlistScraper.m */; }; + CB74D1152480E506002B2079 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB32D2AD21902D9D00B8CD68 /* IOKit.framework */; }; + CB74D1162480E506002B2079 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9E901D0F397FFA006DE6E4 /* Security.framework */; }; + CB74D1182480E506002B2079 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97325FDCFA39411CA2CEA /* Foundation.framework */; }; + CB74D1192480E506002B2079 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; + CB74D11F2480E55D002B2079 /* DaemonMain.m in Sources */ = {isa = PBXBuildFile; fileRef = CB74D1032480E4D9002B2079 /* DaemonMain.m */; }; + CB74D1202480E566002B2079 /* SCDaemon.m in Sources */ = {isa = PBXBuildFile; fileRef = CB74D0FD2480E3E6002B2079 /* SCDaemon.m */; }; + CB8086D424837607004B88BD /* SCDaemonXPC.m in Sources */ = {isa = PBXBuildFile; fileRef = CB8086D324837607004B88BD /* SCDaemonXPC.m */; }; + CB81A94825B7B5B5006956F7 /* SCMigrationUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = CB81A94625B7B5B5006956F7 /* SCMigrationUtilities.h */; }; + CB81A94925B7B5B5006956F7 /* SCMigrationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */; }; + CB81A94A25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */; }; + CB81A94B25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */; }; + CB81A94C25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */; }; + CB81A94D25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */; }; + CB81A94E25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */; }; + CB81A9CF25B7C269006956F7 /* SCBlockUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = CB81A9CD25B7C269006956F7 /* SCBlockUtilities.h */; }; + CB81A9D025B7C269006956F7 /* SCBlockUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */; }; + CB81A9D125B7C269006956F7 /* SCBlockUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */; }; + CB81A9D225B7C269006956F7 /* SCBlockUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */; }; + CB81A9D325B7C269006956F7 /* SCBlockUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */; }; + CB81A9D425B7C269006956F7 /* SCBlockUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */; }; + CB81A9D525B7C269006956F7 /* SCBlockUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */; }; + CB81A9F225B7C5F7006956F7 /* SCBlockFileReaderWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = CB81A9F025B7C5F7006956F7 /* SCBlockFileReaderWriter.h */; }; + CB81A9F325B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */; }; + CB81A9F425B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */; }; + CB81A9F525B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */; }; + CB81A9F625B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */; }; + CB81A9F725B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */; }; + CB81A9F825B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */; }; + CB81AA3A25B7D152006956F7 /* SCHelperToolUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = CB81AA3825B7D152006956F7 /* SCHelperToolUtilities.h */; }; + CB81AA3C25B7D152006956F7 /* SCHelperToolUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AA3925B7D152006956F7 /* SCHelperToolUtilities.m */; }; + CB81AA3E25B7D152006956F7 /* SCHelperToolUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AA3925B7D152006956F7 /* SCHelperToolUtilities.m */; }; + CB81AA3F25B7D152006956F7 /* SCHelperToolUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AA3925B7D152006956F7 /* SCHelperToolUtilities.m */; }; + CB81AA4025B7D152006956F7 /* SCHelperToolUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AA3925B7D152006956F7 /* SCHelperToolUtilities.m */; }; + CB81AAB725B7E6C7006956F7 /* DeprecationSilencers.h in Headers */ = {isa = PBXBuildFile; fileRef = CB81AAB625B7E6C7006956F7 /* DeprecationSilencers.h */; }; + CB81AB2525B7EFA4006956F7 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB81AB2325B7EF74006956F7 /* Sparkle.framework */; }; + CB81AB2625B7EFA4006956F7 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CB81AB2325B7EF74006956F7 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + CB81AB8A25B8E6BE006956F7 /* SCBlockEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */; }; + CB81AB8B25B8E6BE006956F7 /* SCBlockEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */; }; + CB81AB8C25B8E6BE006956F7 /* SCBlockEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */; }; + CB81AB8D25B8E6BE006956F7 /* SCBlockEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */; }; + CB81AB8E25B8E6BE006956F7 /* SCBlockEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */; }; + CB850F3925130F5300EE2E2D /* NSString+IPAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = CB25806516C237F10059C99A /* NSString+IPAddress.m */; }; CB90BF830F49F430006D202D /* HostImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = CB90BF820F49F430006D202D /* HostImporter.m */; }; CB9365620F8581B000EF284E /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CB9365610F8581B000EF284E /* dsa_pub.pem */; }; CB9366E80F85BEF100EF284E /* NSRemoveTemplate.jpg in Resources */ = {isa = PBXBuildFile; fileRef = CB9366E60F85BEF100EF284E /* NSRemoveTemplate.jpg */; }; @@ -52,7 +123,6 @@ CB9C812219CFBB3800CDCAE1 /* PacketFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCA91111960D87300AFD20C /* PacketFilter.m */; }; CB9C812319CFBB4400CDCAE1 /* LaunchctlHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC2F8570F4672FE00CF2A42 /* LaunchctlHelper.m */; }; CB9C812419CFBB4E00CDCAE1 /* HostFileBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB0AE290FA74566006229B3 /* HostFileBlocker.m */; }; - CB9C812519CFBB5500CDCAE1 /* HelperCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = CBD266AE11ED7D9C00042CD8 /* HelperCommon.m */; }; CB9C812619CFBB5E00CDCAE1 /* BlockManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CB25806116C1FDBE0059C99A /* BlockManager.m */; }; CB9C812719CFBB6400CDCAE1 /* NSString+IPAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = CB25806516C237F10059C99A /* NSString+IPAddress.m */; }; CB9C812819CFBB7B00CDCAE1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9E901D0F397FFA006DE6E4 /* Security.framework */; }; @@ -61,12 +131,17 @@ CB9C812F19CFBBB900CDCAE1 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9C812B19CFBB8400CDCAE1 /* Cocoa.framework */; }; CB9C813019CFBBC000CDCAE1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9E901D0F397FFA006DE6E4 /* Security.framework */; }; CB9C813219CFBBE200CDCAE1 /* SCKillerHelper in Copy Helper Tools */ = {isa = PBXBuildFile; fileRef = CB9C811B19CFBA8500CDCAE1 /* SCKillerHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - CBA2AFD90F39EC46005AFEBE /* HelperMain.m in Sources */ = {isa = PBXBuildFile; fileRef = CBA2AFD80F39EC46005AFEBE /* HelperMain.m */; }; + CBA2AFD90F39EC46005AFEBE /* cli-main.m in Sources */ = {isa = PBXBuildFile; fileRef = CBA2AFD80F39EC46005AFEBE /* cli-main.m */; }; + CBADC27E25B22BC7000EE5BB /* SCSentry.m in Sources */ = {isa = PBXBuildFile; fileRef = CBADC27D25B22BC7000EE5BB /* SCSentry.m */; }; + CBADC27F25B22BC7000EE5BB /* SCSentry.m in Sources */ = {isa = PBXBuildFile; fileRef = CBADC27D25B22BC7000EE5BB /* SCSentry.m */; }; + CBADC28025B22BC7000EE5BB /* SCSentry.m in Sources */ = {isa = PBXBuildFile; fileRef = CBADC27D25B22BC7000EE5BB /* SCSentry.m */; }; + CBADC28125B22BC7000EE5BB /* SCSentry.m in Sources */ = {isa = PBXBuildFile; fileRef = CBADC27D25B22BC7000EE5BB /* SCSentry.m */; }; + CBADC28225B22BC7000EE5BB /* SCSentry.m in Sources */ = {isa = PBXBuildFile; fileRef = CBADC27D25B22BC7000EE5BB /* SCSentry.m */; }; CBB0AE2A0FA74566006229B3 /* HostFileBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB0AE290FA74566006229B3 /* HostFileBlocker.m */; }; - CBB1731520F041F4007FCAE9 /* SCUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCUtilities.m */; }; - CBB1731920F05C07007FCAE9 /* SCUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCUtilities.m */; }; - CBB1731B20F05C09007FCAE9 /* SCUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCUtilities.m */; }; - CBB1731C20F05C0A007FCAE9 /* SCUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCUtilities.m */; }; + CBB1731520F041F4007FCAE9 /* SCMiscUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */; }; + CBB1731920F05C07007FCAE9 /* SCMiscUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */; }; + CBB1731B20F05C09007FCAE9 /* SCMiscUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */; }; + CBB1731C20F05C0A007FCAE9 /* SCMiscUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */; }; CBB3FD7A0F53834B00244132 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9E901D0F397FFA006DE6E4 /* Security.framework */; }; CBB7DEEA0F53313F00ABF3EA /* DomainListWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB637220F3E296000EBD135 /* DomainListWindowController.m */; }; CBBCA14B0F54E1B300C75324 /* org.eyebeam.SelfControl.plist in Resources */ = {isa = PBXBuildFile; fileRef = CB4295C20F53EF8C008E10CA /* org.eyebeam.SelfControl.plist */; }; @@ -77,20 +152,24 @@ CBBF4EE515830D7300E364D9 /* TimerWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBBF4EE715830D7300E364D9 /* TimerWindow.xib */; }; CBC2F8580F4672FE00CF2A42 /* LaunchctlHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC2F8570F4672FE00CF2A42 /* LaunchctlHelper.m */; }; CBCA91121960D87300AFD20C /* PacketFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCA91111960D87300AFD20C /* PacketFilter.m */; }; - CBD266B011ED7D9C00042CD8 /* HelperCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = CBD266AE11ED7D9C00042CD8 /* HelperCommon.m */; }; CBD2677011ED92DE00042CD8 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9E90190F397FF6006DE6E4 /* CoreFoundation.framework */; }; CBD2677311ED92EF00042CD8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97325FDCFA39411CA2CEA /* Foundation.framework */; }; CBD2677511ED92F800042CD8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; CBD4848A19D764440020F949 /* PreferencesGeneralViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBD4848819D764440020F949 /* PreferencesGeneralViewController.m */; }; CBD4848F19D768C90020F949 /* PreferencesAdvancedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBD4848D19D768C90020F949 /* PreferencesAdvancedViewController.m */; }; CBDB118C2084239D0010397E /* FirstTime.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBDB118E2084239D0010397E /* FirstTime.xib */; }; - CBDF919A225C5A9700358B95 /* SCUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCUtilities.m */; }; - CBE2BB5619D10CCC0077124F /* SCKillerHelper in Copy Helper Tools */ = {isa = PBXBuildFile; fileRef = CB9C811B19CFBA8500CDCAE1 /* SCKillerHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + CBDF919A225C5A9700358B95 /* SCMiscUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */; }; + CBDFFF4724A0450200622CEE /* org.eyebeam.selfcontrold in Copy Daemon Launch Service */ = {isa = PBXBuildFile; fileRef = CB74D11D2480E506002B2079 /* org.eyebeam.selfcontrold */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + CBDFFF4924A0711800622CEE /* org.eyebeam.selfcontrold.plist in Resources */ = {isa = PBXBuildFile; fileRef = CB8086D524837734004B88BD /* org.eyebeam.selfcontrold.plist */; }; + CBE2BB5619D10CCC0077124F /* SCKillerHelper in Copy Executable Helper Tools */ = {isa = PBXBuildFile; fileRef = CB9C811B19CFBA8500CDCAE1 /* SCKillerHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; CBE4401B0F4BE0670062A1FE /* ThunderbirdPreferenceParser.m in Sources */ = {isa = PBXBuildFile; fileRef = CBE4401A0F4BE0670062A1FE /* ThunderbirdPreferenceParser.m */; }; CBE44FEB19E50900004E9706 /* AllowlistScraper.m in Sources */ = {isa = PBXBuildFile; fileRef = CB73615F19E4FDA000E0924F /* AllowlistScraper.m */; }; CBE5C40B0F4D4531003DB900 /* ButtonWithPopupMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = CBE5C40A0F4D4531003DB900 /* ButtonWithPopupMenu.m */; }; + CBED7D9925ABB911003080D6 /* selfcontrol-cli in Copy Executable Helper Tools */ = {isa = PBXBuildFile; fileRef = CBA2AFD20F39EC12005AFEBE /* selfcontrol-cli */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; CBEE50C10F48C21F00F5DF1C /* TimerWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBEE50C00F48C21F00F5DF1C /* TimerWindowController.m */; }; CBF3B574217BADD7006D5F52 /* SCSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF3B573217BADD7006D5F52 /* SCSettings.m */; }; + D2C22E71F82365403602D7B8 /* libPods-SCKillerHelper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6849B132CC615D050806CDA7 /* libPods-SCKillerHelper.a */; }; + ED0A588EF1802EAF639D2925 /* Pods_SelfControl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76753023D2020A5974B2D62C /* Pods_SelfControl.framework */; }; F5B8CBEE19EE21C30026F3A5 /* SCTimeIntervalFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B8CBED19EE21C30026F3A5 /* SCTimeIntervalFormatter.m */; }; /* End PBXBuildFile section */ @@ -102,50 +181,31 @@ remoteGlobalIDString = 8D1107260486CEB800E47090; remoteInfo = SelfControl; }; - CB9C812D19CFBBA300CDCAE1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; - proxyType = 1; - remoteGlobalIDString = CB9C811A19CFBA8500CDCAE1; - remoteInfo = SCKillerHelper; - }; - CBE2BB5419D10CBB0077124F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; - proxyType = 1; - remoteGlobalIDString = CB9C811A19CFBA8500CDCAE1; - remoteInfo = SCKillerHelper; - }; - CBEF43CF0F7BB7F6009A9FDF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; - proxyType = 1; - remoteGlobalIDString = CBA2AFD10F39EC12005AFEBE; - remoteInfo = org.eyebeam.SelfControl; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - CB2359D90F4541AB0030F59C /* Copy Helper Tools */ = { + CB2359D90F4541AB0030F59C /* Copy Executable Helper Tools */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 6; files = ( - CBE2BB5619D10CCC0077124F /* SCKillerHelper in Copy Helper Tools */, - CB1ED4480F56686400EFECEE /* org.eyebeam.SelfControl in Copy Helper Tools */, + CBED7D9925ABB911003080D6 /* selfcontrol-cli in Copy Executable Helper Tools */, + CBE2BB5619D10CCC0077124F /* SCKillerHelper in Copy Executable Helper Tools */, ); - name = "Copy Helper Tools"; + name = "Copy Executable Helper Tools"; runOnlyForDeploymentPostprocessing = 0; }; - CB9C811919CFBA8500CDCAE1 /* CopyFiles */ = { + CB81AB2725B7EFA4006956F7 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; + dstPath = ""; + dstSubfolderSpec = 10; files = ( + CB81AB2625B7EFA4006956F7 /* Sparkle.framework in Embed Frameworks */, ); - runOnlyForDeploymentPostprocessing = 1; + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; }; CB9C813119CFBBD300CDCAE1 /* Copy Helper Tools */ = { isa = PBXCopyFilesBuildPhase; @@ -158,9 +218,21 @@ name = "Copy Helper Tools"; runOnlyForDeploymentPostprocessing = 0; }; + CBDFFF4624A044C900622CEE /* Copy Daemon Launch Service */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Contents/Library/LaunchServices; + dstSubfolderSpec = 1; + files = ( + CBDFFF4724A0450200622CEE /* org.eyebeam.selfcontrold in Copy Daemon Launch Service */, + ); + name = "Copy Daemon Launch Service"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 04A4C3645D49A017F4B89A0F /* Pods-SCKillerHelper.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SCKillerHelper.release.xcconfig"; path = "Pods/Target Support Files/Pods-SCKillerHelper/Pods-SCKillerHelper.release.xcconfig"; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; @@ -168,19 +240,30 @@ 32CA4F630368D1EE00C91783 /* SelfControl_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelfControl_Prefix.pch; sourceTree = ""; }; 3EF418F71CC7F7FA002D99E8 /* nl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 3EF418F81CC7F7FA002D99E8 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 59B433289D7F3EC8A3F9632F /* libPods-SelfControl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelfControl.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 40D07D2167368D5ED474897D /* Pods-SelfControl Killer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl Killer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer.debug.xcconfig"; sourceTree = ""; }; + 51978917EB66684CE8DAD8C7 /* Pods-SelfControl Killer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl Killer.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer.release.xcconfig"; sourceTree = ""; }; + 5D0CD260CB742C9423EB8A83 /* Pods-SCKillerHelper.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SCKillerHelper.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SCKillerHelper/Pods-SCKillerHelper.debug.xcconfig"; sourceTree = ""; }; 5F489F28028DC38C046B36D3 /* Pods-SelfControl.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl/Pods-SelfControl.release.xcconfig"; sourceTree = ""; }; 640D40CB16D6E962003034B3 /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 640D40CC16D6E962003034B3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; + 6494AB6FC557A9B46A222422 /* libPods-org.eyebeam.selfcontrold.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-org.eyebeam.selfcontrold.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6849B132CC615D050806CDA7 /* libPods-SCKillerHelper.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SCKillerHelper.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 76753023D2020A5974B2D62C /* Pods_SelfControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BA749B3BC24DC33425F27DF /* Pods_SelfControl_Killer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl_Killer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8D1107320486CEB800E47090 /* SelfControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SelfControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E88BFAB5B19695DD021D54 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-org.eyebeam.selfcontrold.debug.xcconfig"; path = "Pods/Target Support Files/Pods-org.eyebeam.selfcontrold/Pods-org.eyebeam.selfcontrold.debug.xcconfig"; sourceTree = ""; }; 949F9F8815B32EC6007B8B42 /* zh-Hans */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 949F9F8D15B333A1007B8B42 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; + B1812886BF1D4F103F3E8D1D /* Pods-org.eyebeam.selfcontrold.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-org.eyebeam.selfcontrold.release.xcconfig"; path = "Pods/Target Support Files/Pods-org.eyebeam.selfcontrold/Pods-org.eyebeam.selfcontrold.release.xcconfig"; sourceTree = ""; }; CB0EEF5D20FD8CE00024D27B /* SelfControlTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SelfControlTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CB0EEF6120FD8CE00024D27B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CB0EEF7720FE49020024D27B /* SCUtilitiesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCUtilitiesTests.m; sourceTree = ""; }; + CB0EEF7720FE49020024D27B /* SCUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCUtilityTests.m; sourceTree = ""; }; + CB1465B625B027E700130D2E /* SCErr.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SCErr.h; path = Common/SCErr.h; sourceTree = SOURCE_ROOT; }; + CB1465B725B027E700130D2E /* SCErr.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SCErr.m; path = Common/SCErr.m; sourceTree = SOURCE_ROOT; }; + CB1465C325B0285300130D2E /* SCError.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = SCError.strings; sourceTree = ""; }; CB1B44BD23B7F1ED00EBA087 /* cheater-background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cheater-background.png"; sourceTree = ""; }; - CB1E7A6D0F9AEA9B00D158BC /* ERRORS */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ERRORS; sourceTree = ""; }; + CB1CA68425ABAA360084A551 /* selfcontrol-cli-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "selfcontrol-cli-Info.plist"; sourceTree = ""; }; CB20C5D7245699D700B9D749 /* version-header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "version-header.h"; sourceTree = ""; }; CB249FEC19D782230087BBB6 /* SelfControlIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = SelfControlIcon.icns; sourceTree = ""; }; CB25806016C1FDBE0059C99A /* BlockManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockManager.h; sourceTree = ""; }; @@ -199,12 +282,40 @@ CB587E4F0F50FE8800C66A09 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = ""; }; CB5DFCB52251DD1F0084CEC2 /* SCConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCConstants.h; sourceTree = ""; }; CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCConstants.m; sourceTree = ""; }; + CB62FC2F24B11A4F00ADBC40 /* selfcontrold-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "selfcontrold-Info.plist"; sourceTree = ""; }; + CB62FC3924B124B900ADBC40 /* SCXPCClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCXPCClient.h; sourceTree = ""; }; + CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCXPCClient.m; sourceTree = ""; }; + CB62FC3C24B1298500ADBC40 /* SCDaemonBlockMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCDaemonBlockMethods.h; sourceTree = ""; }; + CB62FC3D24B1298500ADBC40 /* SCDaemonBlockMethods.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCDaemonBlockMethods.m; sourceTree = ""; }; CB6840A4247A3E5500E51564 /* SelfControlKillerIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = SelfControlKillerIcon.icns; sourceTree = ""; }; + CB69C4EC25A3FD8A0030CFCD /* SCXPCAuthorization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCXPCAuthorization.h; sourceTree = ""; }; + CB69C4ED25A3FD8A0030CFCD /* SCXPCAuthorization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCXPCAuthorization.m; sourceTree = ""; }; CB6ED5662085C70200012817 /* da */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = da; path = da.lproj/FirstTime.xib; sourceTree = ""; }; CB7026181CD7177400D7C7F0 /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; CB7026191CD7177400D7C7F0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; CB73615E19E4FDA000E0924F /* AllowlistScraper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AllowlistScraper.h; sourceTree = ""; }; CB73615F19E4FDA000E0924F /* AllowlistScraper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AllowlistScraper.m; sourceTree = ""; }; + CB74D0FC2480E3E6002B2079 /* SCDaemon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCDaemon.h; sourceTree = ""; }; + CB74D0FD2480E3E6002B2079 /* SCDaemon.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCDaemon.m; sourceTree = ""; }; + CB74D1032480E4D9002B2079 /* DaemonMain.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DaemonMain.m; sourceTree = ""; }; + CB74D11D2480E506002B2079 /* org.eyebeam.selfcontrold */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = org.eyebeam.selfcontrold; sourceTree = BUILT_PRODUCTS_DIR; }; + CB74D122248374E6002B2079 /* SCDaemonProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCDaemonProtocol.h; sourceTree = ""; }; + CB8086D224837607004B88BD /* SCDaemonXPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCDaemonXPC.h; sourceTree = ""; }; + CB8086D324837607004B88BD /* SCDaemonXPC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCDaemonXPC.m; sourceTree = ""; }; + CB8086D524837734004B88BD /* org.eyebeam.selfcontrold.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = org.eyebeam.selfcontrold.plist; sourceTree = ""; }; + CB81A94625B7B5B5006956F7 /* SCMigrationUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCMigrationUtilities.h; sourceTree = ""; }; + CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCMigrationUtilities.m; sourceTree = ""; }; + CB81A9CD25B7C269006956F7 /* SCBlockUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCBlockUtilities.h; sourceTree = ""; }; + CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCBlockUtilities.m; sourceTree = ""; }; + CB81A9E225B7C2A8006956F7 /* SCUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCUtility.h; sourceTree = ""; }; + CB81A9F025B7C5F7006956F7 /* SCBlockFileReaderWriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCBlockFileReaderWriter.h; sourceTree = ""; }; + CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCBlockFileReaderWriter.m; sourceTree = ""; }; + CB81AA3825B7D152006956F7 /* SCHelperToolUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCHelperToolUtilities.h; sourceTree = ""; }; + CB81AA3925B7D152006956F7 /* SCHelperToolUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCHelperToolUtilities.m; sourceTree = ""; }; + CB81AAB625B7E6C7006956F7 /* DeprecationSilencers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecationSilencers.h; sourceTree = ""; }; + CB81AB2325B7EF74006956F7 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; + CB81AB8825B8E6BE006956F7 /* SCBlockEntry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCBlockEntry.h; sourceTree = ""; }; + CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCBlockEntry.m; sourceTree = ""; }; CB90BF810F49F430006D202D /* HostImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HostImporter.h; sourceTree = ""; }; CB90BF820F49F430006D202D /* HostImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HostImporter.m; sourceTree = ""; }; CB9365610F8581B000EF284E /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = ""; }; @@ -223,13 +334,14 @@ CB9C812B19CFBB8400CDCAE1 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; CB9E90190F397FF6006DE6E4 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; CB9E901D0F397FFA006DE6E4 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - CBA2AFD20F39EC12005AFEBE /* org.eyebeam.SelfControl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = org.eyebeam.SelfControl; sourceTree = BUILT_PRODUCTS_DIR; }; - CBA2AFD70F39EC46005AFEBE /* HelperMain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelperMain.h; sourceTree = ""; }; - CBA2AFD80F39EC46005AFEBE /* HelperMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelperMain.m; sourceTree = ""; }; + CBA2AFD20F39EC12005AFEBE /* selfcontrol-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "selfcontrol-cli"; sourceTree = BUILT_PRODUCTS_DIR; }; + CBA2AFD80F39EC46005AFEBE /* cli-main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "cli-main.m"; sourceTree = ""; }; + CBADC27C25B22BC7000EE5BB /* SCSentry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCSentry.h; sourceTree = ""; }; + CBADC27D25B22BC7000EE5BB /* SCSentry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCSentry.m; sourceTree = ""; }; CBB0AE280FA74566006229B3 /* HostFileBlocker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HostFileBlocker.h; sourceTree = ""; }; CBB0AE290FA74566006229B3 /* HostFileBlocker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HostFileBlocker.m; sourceTree = ""; }; - CBB1731220F041F4007FCAE9 /* SCUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCUtilities.h; sourceTree = ""; }; - CBB1731320F041F4007FCAE9 /* SCUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCUtilities.m; sourceTree = ""; }; + CBB1731220F041F4007FCAE9 /* SCMiscUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCMiscUtilities.h; sourceTree = ""; }; + CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCMiscUtilities.m; sourceTree = ""; }; CBB60BE31F12F19E00DCB597 /* distribution-build.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = "distribution-build.rb"; sourceTree = ""; }; CBB637210F3E296000EBD135 /* DomainListWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DomainListWindowController.h; sourceTree = ""; }; CBB637220F3E296000EBD135 /* DomainListWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DomainListWindowController.m; sourceTree = ""; }; @@ -249,13 +361,8 @@ CBC2F8650F4674E300CF2A42 /* LaunchctlHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LaunchctlHelper.h; sourceTree = ""; }; CBCA91101960D87300AFD20C /* PacketFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketFilter.h; sourceTree = ""; }; CBCA91111960D87300AFD20C /* PacketFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PacketFilter.m; sourceTree = ""; }; - CBCA9116196123E600AFD20C /* SelfControl.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = SelfControl.entitlements; sourceTree = ""; }; CBCA91271961381F00AFD20C /* tr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; CBCA912B1961384600AFD20C /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; - CBD266AD11ED7D9C00042CD8 /* HelperCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelperCommon.h; sourceTree = ""; }; - CBD266AE11ED7D9C00042CD8 /* HelperCommon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelperCommon.m; sourceTree = ""; }; - CBD266E111ED84F700042CD8 /* SelfControlCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelfControlCommon.h; sourceTree = ""; }; - CBD4848219D75F6D0020F949 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; CBD4848519D7611F0020F949 /* Podfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Podfile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; CBD4848619D7611F0020F949 /* TemplateIcon2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TemplateIcon2x.png; sourceTree = ""; }; CBD4848719D764440020F949 /* PreferencesGeneralViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesGeneralViewController.h; sourceTree = ""; }; @@ -329,6 +436,7 @@ CBDB11942084801B0010397E /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/PreferencesAdvancedViewController.strings; sourceTree = ""; }; CBDB11962084801B0010397E /* da */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; CBDB11972084801C0010397E /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; + CBDFFF4B24A07DB300622CEE /* SelfControl.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SelfControl.entitlements; sourceTree = ""; }; CBE440190F4BE0670062A1FE /* ThunderbirdPreferenceParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThunderbirdPreferenceParser.h; sourceTree = ""; }; CBE4401A0F4BE0670062A1FE /* ThunderbirdPreferenceParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThunderbirdPreferenceParser.m; sourceTree = ""; }; CBE5C4090F4D4531003DB900 /* ButtonWithPopupMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ButtonWithPopupMenu.h; sourceTree = ""; }; @@ -338,8 +446,11 @@ CBF3B572217BADD7006D5F52 /* SCSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCSettings.h; sourceTree = ""; }; CBF3B573217BADD7006D5F52 /* SCSettings.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCSettings.m; sourceTree = ""; }; DBA01017E61D6B843CA3758D /* Pods-SelfControl.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl/Pods-SelfControl.debug.xcconfig"; sourceTree = ""; }; + EA6CDF7FADBBC7B9A7694C8B /* Pods-selfcontrol-cli.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-selfcontrol-cli.debug.xcconfig"; path = "Pods/Target Support Files/Pods-selfcontrol-cli/Pods-selfcontrol-cli.debug.xcconfig"; sourceTree = ""; }; + F2797D75520FA7BABC7C2B07 /* Pods-selfcontrol-cli.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-selfcontrol-cli.release.xcconfig"; path = "Pods/Target Support Files/Pods-selfcontrol-cli/Pods-selfcontrol-cli.release.xcconfig"; sourceTree = ""; }; F5B8CBEC19EE21C30026F3A5 /* SCTimeIntervalFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCTimeIntervalFormatter.h; sourceTree = ""; }; F5B8CBED19EE21C30026F3A5 /* SCTimeIntervalFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCTimeIntervalFormatter.m; sourceTree = ""; }; + F946636EDF2639CE7629BF6C /* libPods-selfcontrol-cli.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-selfcontrol-cli.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -348,10 +459,11 @@ buildActionMask = 2147483647; files = ( CB32D2AE21902D9D00B8CD68 /* IOKit.framework in Frameworks */, + CB81AB2525B7EFA4006956F7 /* Sparkle.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, CB587E500F50FE8800C66A09 /* SystemConfiguration.framework in Frameworks */, CBB3FD7A0F53834B00244132 /* Security.framework in Frameworks */, - AA0270C7A1D37351F38296D7 /* libPods-SelfControl.a in Frameworks */, + ED0A588EF1802EAF639D2925 /* Pods_SelfControl.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -371,6 +483,19 @@ CBD2677011ED92DE00042CD8 /* CoreFoundation.framework in Frameworks */, CBD2677311ED92EF00042CD8 /* Foundation.framework in Frameworks */, CBD2677511ED92F800042CD8 /* Cocoa.framework in Frameworks */, + 051230B633C4B37DD5CF305E /* libPods-selfcontrol-cli.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CB74D1142480E506002B2079 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CB74D1152480E506002B2079 /* IOKit.framework in Frameworks */, + CB74D1162480E506002B2079 /* Security.framework in Frameworks */, + CB74D1182480E506002B2079 /* Foundation.framework in Frameworks */, + CB74D1192480E506002B2079 /* Cocoa.framework in Frameworks */, + 01EFCB03830117674B4E6B21 /* libPods-org.eyebeam.selfcontrold.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -381,6 +506,7 @@ CB32D2B121902DB300B8CD68 /* IOKit.framework in Frameworks */, CB9C813019CFBBC000CDCAE1 /* Security.framework in Frameworks */, CB9C812F19CFBBB900CDCAE1 /* Cocoa.framework in Frameworks */, + 9A31F3D484C646257DB308C5 /* Pods_SelfControl_Killer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -392,6 +518,7 @@ CB9C812C19CFBB8400CDCAE1 /* Cocoa.framework in Frameworks */, CB9C812A19CFBB8000CDCAE1 /* Foundation.framework in Frameworks */, CB9C812819CFBB7B00CDCAE1 /* Security.framework in Frameworks */, + D2C22E71F82365403602D7B8 /* libPods-SCKillerHelper.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -401,10 +528,10 @@ 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { isa = PBXGroup; children = ( - CBD4848219D75F6D0020F949 /* Sparkle.framework */, 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, CB587E4F0F50FE8800C66A09 /* SystemConfiguration.framework */, CB9E901D0F397FFA006DE6E4 /* Security.framework */, + CB81AB2325B7EF74006956F7 /* Sparkle.framework */, ); name = "Linked Frameworks"; sourceTree = ""; @@ -423,10 +550,11 @@ isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* SelfControl.app */, - CBA2AFD20F39EC12005AFEBE /* org.eyebeam.SelfControl */, + CBA2AFD20F39EC12005AFEBE /* selfcontrol-cli */, CB9C80F719CFB79700CDCAE1 /* SelfControl Killer.app */, CB9C811B19CFBA8500CDCAE1 /* SCKillerHelper */, CB0EEF5D20FD8CE00024D27B /* SelfControlTests.xctest */, + CB74D11D2480E506002B2079 /* org.eyebeam.selfcontrold */, ); name = Products; sourceTree = ""; @@ -434,12 +562,16 @@ 29B97314FDCFA39411CA2CEA /* SelfControl */ = { isa = PBXGroup; children = ( + CBDFFF4B24A07DB300622CEE /* SelfControl.entitlements */, CB20C5D7245699D700B9D749 /* version-header.h */, CBD4848519D7611F0020F949 /* Podfile */, CBD4848619D7611F0020F949 /* TemplateIcon2x.png */, - CBCA9116196123E600AFD20C /* SelfControl.entitlements */, - CB1E7A6D0F9AEA9B00D158BC /* ERRORS */, - CB4294DF0F53D865008E10CA /* Classes */, + CBD266C611ED82DB00042CD8 /* CLI */, + CB74D121248371E7002B2079 /* Daemon */, + CB4294DF0F53D865008E10CA /* App Classes */, + CB81AB8725B8E67D006956F7 /* Block Management */, + CB1CA64C25ABA5990084A551 /* Common */, + CB9C80F819CFB79700CDCAE1 /* SelfControl Killer */, CB0EEF5E20FD8CE00024D27B /* SelfControlTests */, 29B97323FDCFA39411CA2CEA /* Frameworks */, CB4294F10F53D95D008E10CA /* Interfaces */, @@ -448,7 +580,6 @@ 29B97317FDCFA39411CA2CEA /* Resources */, CB42939A0F53A819008E10CA /* COPYING */, CBB60BE41F13441F00DCB597 /* Distribution Build */, - CB9C80F819CFB79700CDCAE1 /* SelfControl Killer */, DDDC709FF9E9196A96CA7BCF /* Pods */, ); name = SelfControl; @@ -476,6 +607,7 @@ 8D1107310486CEB800E47090 /* Info.plist */, CB249FEC19D782230087BBB6 /* SelfControlIcon.icns */, CBBF4E891582F8BD00E364D9 /* InfoPlist.strings */, + CB1465C325B0285300130D2E /* SCError.strings */, ); name = Resources; sourceTree = ""; @@ -488,7 +620,11 @@ CB9C812919CFBB8000CDCAE1 /* Foundation.framework */, 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, - 59B433289D7F3EC8A3F9632F /* libPods-SelfControl.a */, + 76753023D2020A5974B2D62C /* Pods_SelfControl.framework */, + 8BA749B3BC24DC33425F27DF /* Pods_SelfControl_Killer.framework */, + F946636EDF2639CE7629BF6C /* libPods-selfcontrol-cli.a */, + 6849B132CC615D050806CDA7 /* libPods-SCKillerHelper.a */, + 6494AB6FC557A9B46A222422 /* libPods-org.eyebeam.selfcontrold.a */, ); name = Frameworks; sourceTree = ""; @@ -496,16 +632,36 @@ CB0EEF5E20FD8CE00024D27B /* SelfControlTests */ = { isa = PBXGroup; children = ( - CB0EEF7720FE49020024D27B /* SCUtilitiesTests.m */, + CB0EEF7720FE49020024D27B /* SCUtilityTests.m */, CB0EEF6120FD8CE00024D27B /* Info.plist */, ); path = SelfControlTests; sourceTree = ""; }; - CB4294DF0F53D865008E10CA /* Classes */ = { + CB1CA64C25ABA5990084A551 /* Common */ = { + isa = PBXGroup; + children = ( + CB81A9E325B7C2B9006956F7 /* Utility */, + CBADC27C25B22BC7000EE5BB /* SCSentry.h */, + CBADC27D25B22BC7000EE5BB /* SCSentry.m */, + CB81A9F025B7C5F7006956F7 /* SCBlockFileReaderWriter.h */, + CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */, + CB1465B625B027E700130D2E /* SCErr.h */, + CB1465B725B027E700130D2E /* SCErr.m */, + CBF3B572217BADD7006D5F52 /* SCSettings.h */, + CBF3B573217BADD7006D5F52 /* SCSettings.m */, + CB69C4EC25A3FD8A0030CFCD /* SCXPCAuthorization.h */, + CB69C4ED25A3FD8A0030CFCD /* SCXPCAuthorization.m */, + CB62FC3924B124B900ADBC40 /* SCXPCClient.h */, + CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */, + CB81AAB625B7E6C7006956F7 /* DeprecationSilencers.h */, + ); + path = Common; + sourceTree = ""; + }; + CB4294DF0F53D865008E10CA /* App Classes */ = { isa = PBXGroup; children = ( - CBD266E111ED84F700042CD8 /* SelfControlCommon.h */, CB25806416C237F10059C99A /* NSString+IPAddress.h */, CB25806516C237F10059C99A /* NSString+IPAddress.m */, CBE5C4090F4D4531003DB900 /* ButtonWithPopupMenu.h */, @@ -518,32 +674,15 @@ CBD4848D19D768C90020F949 /* PreferencesAdvancedViewController.m */, CBB637210F3E296000EBD135 /* DomainListWindowController.h */, CBB637220F3E296000EBD135 /* DomainListWindowController.m */, - CBD266C611ED82DB00042CD8 /* Helper Tools */, - CBB0AE280FA74566006229B3 /* HostFileBlocker.h */, - CBB0AE290FA74566006229B3 /* HostFileBlocker.m */, - CBCA91101960D87300AFD20C /* PacketFilter.h */, - CBCA91111960D87300AFD20C /* PacketFilter.m */, - CB25806016C1FDBE0059C99A /* BlockManager.h */, - CB25806116C1FDBE0059C99A /* BlockManager.m */, - CBE440190F4BE0670062A1FE /* ThunderbirdPreferenceParser.h */, - CBE4401A0F4BE0670062A1FE /* ThunderbirdPreferenceParser.m */, - CB90BF810F49F430006D202D /* HostImporter.h */, - CB90BF820F49F430006D202D /* HostImporter.m */, - CB73615E19E4FDA000E0924F /* AllowlistScraper.h */, - CB73615F19E4FDA000E0924F /* AllowlistScraper.m */, CBEE50BF0F48C21F00F5DF1C /* TimerWindowController.h */, CBEE50C00F48C21F00F5DF1C /* TimerWindowController.m */, CBC2F8650F4674E300CF2A42 /* LaunchctlHelper.h */, CBC2F8570F4672FE00CF2A42 /* LaunchctlHelper.m */, - CBF3B572217BADD7006D5F52 /* SCSettings.h */, - CBF3B573217BADD7006D5F52 /* SCSettings.m */, - CBB1731220F041F4007FCAE9 /* SCUtilities.h */, - CBB1731320F041F4007FCAE9 /* SCUtilities.m */, CB5DFCB52251DD1F0084CEC2 /* SCConstants.h */, CB5DFCB62251DD1F0084CEC2 /* SCConstants.m */, F5B8CBEB19EE21340026F3A5 /* Formatters */, ); - name = Classes; + name = "App Classes"; sourceTree = ""; }; CB4294F10F53D95D008E10CA /* Interfaces */ = { @@ -559,6 +698,60 @@ name = Interfaces; sourceTree = ""; }; + CB74D121248371E7002B2079 /* Daemon */ = { + isa = PBXGroup; + children = ( + CB74D1032480E4D9002B2079 /* DaemonMain.m */, + CB74D0FC2480E3E6002B2079 /* SCDaemon.h */, + CB74D0FD2480E3E6002B2079 /* SCDaemon.m */, + CB62FC3C24B1298500ADBC40 /* SCDaemonBlockMethods.h */, + CB62FC3D24B1298500ADBC40 /* SCDaemonBlockMethods.m */, + CB8086D224837607004B88BD /* SCDaemonXPC.h */, + CB8086D324837607004B88BD /* SCDaemonXPC.m */, + CB74D122248374E6002B2079 /* SCDaemonProtocol.h */, + CB62FC2F24B11A4F00ADBC40 /* selfcontrold-Info.plist */, + CB8086D524837734004B88BD /* org.eyebeam.selfcontrold.plist */, + ); + path = Daemon; + sourceTree = ""; + }; + CB81A9E325B7C2B9006956F7 /* Utility */ = { + isa = PBXGroup; + children = ( + CB81A9E225B7C2A8006956F7 /* SCUtility.h */, + CB81A94625B7B5B5006956F7 /* SCMigrationUtilities.h */, + CB81A94725B7B5B5006956F7 /* SCMigrationUtilities.m */, + CB81A9CD25B7C269006956F7 /* SCBlockUtilities.h */, + CB81A9CE25B7C269006956F7 /* SCBlockUtilities.m */, + CBB1731220F041F4007FCAE9 /* SCMiscUtilities.h */, + CBB1731320F041F4007FCAE9 /* SCMiscUtilities.m */, + CB81AA3825B7D152006956F7 /* SCHelperToolUtilities.h */, + CB81AA3925B7D152006956F7 /* SCHelperToolUtilities.m */, + ); + path = Utility; + sourceTree = ""; + }; + CB81AB8725B8E67D006956F7 /* Block Management */ = { + isa = PBXGroup; + children = ( + CB81AB8825B8E6BE006956F7 /* SCBlockEntry.h */, + CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */, + CBB0AE280FA74566006229B3 /* HostFileBlocker.h */, + CBB0AE290FA74566006229B3 /* HostFileBlocker.m */, + CBCA91101960D87300AFD20C /* PacketFilter.h */, + CBCA91111960D87300AFD20C /* PacketFilter.m */, + CB25806016C1FDBE0059C99A /* BlockManager.h */, + CB25806116C1FDBE0059C99A /* BlockManager.m */, + CBE440190F4BE0670062A1FE /* ThunderbirdPreferenceParser.h */, + CBE4401A0F4BE0670062A1FE /* ThunderbirdPreferenceParser.m */, + CB90BF810F49F430006D202D /* HostImporter.h */, + CB90BF820F49F430006D202D /* HostImporter.m */, + CB73615E19E4FDA000E0924F /* AllowlistScraper.h */, + CB73615F19E4FDA000E0924F /* AllowlistScraper.m */, + ); + path = "Block Management"; + sourceTree = ""; + }; CB9C80F819CFB79700CDCAE1 /* SelfControl Killer */ = { isa = PBXGroup; children = ( @@ -600,15 +793,13 @@ name = "Distribution Build"; sourceTree = ""; }; - CBD266C611ED82DB00042CD8 /* Helper Tools */ = { + CBD266C611ED82DB00042CD8 /* CLI */ = { isa = PBXGroup; children = ( - CBA2AFD70F39EC46005AFEBE /* HelperMain.h */, - CBA2AFD80F39EC46005AFEBE /* HelperMain.m */, - CBD266AD11ED7D9C00042CD8 /* HelperCommon.h */, - CBD266AE11ED7D9C00042CD8 /* HelperCommon.m */, + CB1CA68425ABAA360084A551 /* selfcontrol-cli-Info.plist */, + CBA2AFD80F39EC46005AFEBE /* cli-main.m */, ); - name = "Helper Tools"; + name = CLI; sourceTree = ""; }; DDDC709FF9E9196A96CA7BCF /* Pods */ = { @@ -616,6 +807,14 @@ children = ( DBA01017E61D6B843CA3758D /* Pods-SelfControl.debug.xcconfig */, 5F489F28028DC38C046B36D3 /* Pods-SelfControl.release.xcconfig */, + 5D0CD260CB742C9423EB8A83 /* Pods-SCKillerHelper.debug.xcconfig */, + 04A4C3645D49A017F4B89A0F /* Pods-SCKillerHelper.release.xcconfig */, + 40D07D2167368D5ED474897D /* Pods-SelfControl Killer.debug.xcconfig */, + 51978917EB66684CE8DAD8C7 /* Pods-SelfControl Killer.release.xcconfig */, + 92E88BFAB5B19695DD021D54 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */, + B1812886BF1D4F103F3E8D1D /* Pods-org.eyebeam.selfcontrold.release.xcconfig */, + EA6CDF7FADBBC7B9A7694C8B /* Pods-selfcontrol-cli.debug.xcconfig */, + F2797D75520FA7BABC7C2B07 /* Pods-selfcontrol-cli.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -636,6 +835,11 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + CB81A94825B7B5B5006956F7 /* SCMigrationUtilities.h in Headers */, + CB81AA3A25B7D152006956F7 /* SCHelperToolUtilities.h in Headers */, + CB81AAB725B7E6C7006956F7 /* DeprecationSilencers.h in Headers */, + CB81A9CF25B7C269006956F7 /* SCBlockUtilities.h in Headers */, + CB81A9F225B7C5F7006956F7 /* SCBlockFileReaderWriter.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -647,19 +851,19 @@ buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "SelfControl" */; buildPhases = ( 10CA088CE7CF641F4F172445 /* [CP] Check Pods Manifest.lock */, + CB81A92E25B7B4B8006956F7 /* ShellScript */, 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, 8D11072E0486CEB800E47090 /* Frameworks */, - CB2359D90F4541AB0030F59C /* Copy Helper Tools */, + CB2359D90F4541AB0030F59C /* Copy Executable Helper Tools */, + CBDFFF4624A044C900622CEE /* Copy Daemon Launch Service */, CB5E5FF81C3A5FD10038F331 /* ShellScript */, 7D55904F6A3D627B837007A9 /* [CP] Embed Pods Frameworks */, - CBFF218A3108765AAB5DE542 /* [CP] Copy Pods Resources */, + CB81AB2725B7EFA4006956F7 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( - CBE2BB5519D10CBB0077124F /* PBXTargetDependency */, - CBEF43D00F7BB7F6009A9FDF /* PBXTargetDependency */, ); name = SelfControl; productInstallPath = "$(HOME)/Applications"; @@ -686,19 +890,38 @@ productReference = CB0EEF5D20FD8CE00024D27B /* SelfControlTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + CB74D1052480E506002B2079 /* org.eyebeam.selfcontrold */ = { + isa = PBXNativeTarget; + buildConfigurationList = CB74D11A2480E506002B2079 /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */; + buildPhases = ( + 13D18A4223D8447176403B7B /* [CP] Check Pods Manifest.lock */, + CB74D1062480E506002B2079 /* ShellScript */, + CB74D1072480E506002B2079 /* Sources */, + CB74D1142480E506002B2079 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = org.eyebeam.selfcontrold; + productName = "selfcontrol-helper"; + productReference = CB74D11D2480E506002B2079 /* org.eyebeam.selfcontrold */; + productType = "com.apple.product-type.tool"; + }; CB9C80F619CFB79700CDCAE1 /* SelfControl Killer */ = { isa = PBXNativeTarget; buildConfigurationList = CB9C811119CFB79700CDCAE1 /* Build configuration list for PBXNativeTarget "SelfControl Killer" */; buildPhases = ( + 43BCF4BDF8B57ECB6640A9A7 /* [CP] Check Pods Manifest.lock */, CB9C80F319CFB79700CDCAE1 /* Sources */, CB9C80F419CFB79700CDCAE1 /* Frameworks */, CB9C80F519CFB79700CDCAE1 /* Resources */, CB9C813119CFBBD300CDCAE1 /* Copy Helper Tools */, + 4908B8EBDC1C6FDD38B8BC40 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( - CB9C812E19CFBBA300CDCAE1 /* PBXTargetDependency */, ); name = "SelfControl Killer"; productName = "SelfControl Killer"; @@ -709,9 +932,9 @@ isa = PBXNativeTarget; buildConfigurationList = CB9C811F19CFBA8500CDCAE1 /* Build configuration list for PBXNativeTarget "SCKillerHelper" */; buildPhases = ( + FFF74CE7FF453369A49FFE5B /* [CP] Check Pods Manifest.lock */, CB9C811719CFBA8500CDCAE1 /* Sources */, CB9C811819CFBA8500CDCAE1 /* Frameworks */, - CB9C811919CFBA8500CDCAE1 /* CopyFiles */, ); buildRules = ( ); @@ -722,10 +945,11 @@ productReference = CB9C811B19CFBA8500CDCAE1 /* SCKillerHelper */; productType = "com.apple.product-type.tool"; }; - CBA2AFD10F39EC12005AFEBE /* org.eyebeam.SelfControl */ = { + CBA2AFD10F39EC12005AFEBE /* selfcontrol-cli */ = { isa = PBXNativeTarget; - buildConfigurationList = CBA2AFD60F39EC33005AFEBE /* Build configuration list for PBXNativeTarget "org.eyebeam.SelfControl" */; + buildConfigurationList = CBA2AFD60F39EC33005AFEBE /* Build configuration list for PBXNativeTarget "selfcontrol-cli" */; buildPhases = ( + 9480FFC8127CE2B36F02552C /* [CP] Check Pods Manifest.lock */, CB20C5D6245696C500B9D749 /* ShellScript */, CBA2AFCF0F39EC12005AFEBE /* Sources */, CB54D4490F93E32B00AA22E9 /* Frameworks */, @@ -734,9 +958,9 @@ ); dependencies = ( ); - name = org.eyebeam.SelfControl; + name = "selfcontrol-cli"; productName = "selfcontrol-helper"; - productReference = CBA2AFD20F39EC12005AFEBE /* org.eyebeam.SelfControl */; + productReference = CBA2AFD20F39EC12005AFEBE /* selfcontrol-cli */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ @@ -749,6 +973,7 @@ TargetAttributes = { 8D1107260486CEB800E47090 = { DevelopmentTeam = L6W5L88KN7; + LastSwiftMigration = 1150; ProvisioningStyle = Manual; SystemCapabilities = { com.apple.ApplicationGroups.Mac = { @@ -762,6 +987,10 @@ ProvisioningStyle = Automatic; TestTargetID = 8D1107260486CEB800E47090; }; + CB74D1052480E506002B2079 = { + DevelopmentTeam = L6W5L88KN7; + ProvisioningStyle = Manual; + }; CB9C80F619CFB79700CDCAE1 = { CreatedOnToolsVersion = 6.0.1; DevelopmentTeam = L6W5L88KN7; @@ -805,10 +1034,11 @@ projectRoot = ""; targets = ( 8D1107260486CEB800E47090 /* SelfControl */, - CBA2AFD10F39EC12005AFEBE /* org.eyebeam.SelfControl */, + CBA2AFD10F39EC12005AFEBE /* selfcontrol-cli */, CB9C80F619CFB79700CDCAE1 /* SelfControl Killer */, CB9C811A19CFBA8500CDCAE1 /* SCKillerHelper */, CB0EEF5C20FD8CE00024D27B /* SelfControlTests */, + CB74D1052480E506002B2079 /* org.eyebeam.selfcontrold */, ); }; /* End PBXProject section */ @@ -824,9 +1054,10 @@ CB9366E80F85BEF100EF284E /* NSRemoveTemplate.jpg in Resources */, CB9366E90F85BEF100EF284E /* NSAddTemplate.jpg in Resources */, CBDB118C2084239D0010397E /* FirstTime.xib in Resources */, + CB62FC3024B11A4F00ADBC40 /* selfcontrold-Info.plist in Resources */, CBBF4EE515830D7300E364D9 /* TimerWindow.xib in Resources */, + CBDFFF4924A0711800622CEE /* org.eyebeam.selfcontrold.plist in Resources */, CB0385E219D77051004614B6 /* PreferencesGeneralViewController.xib in Resources */, - CB1E7A6E0F9AEA9B00D158BC /* ERRORS in Resources */, CB40A8680FBC7DE700167727 /* SelfControlBlocklist.icns in Resources */, CBBF4E7F1582F7A200E364D9 /* DomainList.xib in Resources */, CBBF4E801582F7A200E364D9 /* MainMenu.xib in Resources */, @@ -835,6 +1066,7 @@ CBBF4E8E1582F8E000E364D9 /* Localizable.strings in Resources */, CB0385E119D77051004614B6 /* PreferencesAdvancedViewController.xib in Resources */, CB1B44BE23B7F1ED00EBA087 /* cheater-background.png in Resources */, + CB1465C425B0285300130D2E /* SCError.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -849,6 +1081,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CB1465C525B0285300130D2E /* SCError.strings in Resources */, CB6840A5247A3E5500E51564 /* SelfControlKillerIcon.icns in Resources */, CB9C810419CFB79700CDCAE1 /* MainMenu.xib in Resources */, ); @@ -875,6 +1108,68 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 13D18A4223D8447176403B7B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-org.eyebeam.selfcontrold-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 43BCF4BDF8B57ECB6640A9A7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SelfControl Killer-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4908B8EBDC1C6FDD38B8BC40 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Sentry-framework/Sentry.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 7D55904F6A3D627B837007A9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -882,19 +1177,45 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-frameworks.sh", - "${PODS_ROOT}/Sparkle/Sparkle.framework", - "${PODS_ROOT}/Sparkle/Sparkle.framework.dSYM", + "${BUILT_PRODUCTS_DIR}/FormatterKit/FormatterKit.framework", + "${BUILT_PRODUCTS_DIR}/LetsMove/LetsMove.framework", + "${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework", + "${BUILT_PRODUCTS_DIR}/Sentry-framework/Sentry.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework", - "${DWARF_DSYM_FOLDER_PATH}/Sparkle.framework.dSYM", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FormatterKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LetsMove.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MASPreferences.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 9480FFC8127CE2B36F02552C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-selfcontrol-cli-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; CB20C5D6245696C500B9D749 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -910,7 +1231,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "buildNumber=$(xcodebuild -project SelfControl.xcodeproj/ -showBuildSettings | grep \"CURRENT_PROJECT_VERSION\" | sed 's/[ ]*CURRENT_PROJECT_VERSION = //')\n\necho \"#define SELFCONTROL_VERSION_STRING @\\\"${buildNumber}\\\"\" > \"${PROJECT_DIR}/version-header.h\"\n"; + shellScript = "# buildNumber=$(xcodebuild -project SelfControl.xcodeproj/ -showBuildSettings | grep \"CURRENT_PROJECT_VERSION\" | sed 's/[ ]*CURRENT_PROJECT_VERSION = //')\necho \"#define SELFCONTROL_VERSION_STRING @\\\"${MARKETING_VERSION}\\\"\" > \"${PROJECT_DIR}/version-header.h\"\n"; }; CB5E5FF81C3A5FD10038F331 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -923,104 +1244,62 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "LOCATION=\"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\n\n# Usually set by Xcode\nCODE_SIGN_IDENTITY=\"Developer ID Application: Charlie Stigler (L6W5L88KN7)\"\n\necho \"location: \"\necho $LOCATION\n\n# codesign --verbose --force --sign \"$CODE_SIGN_IDENTITY\" \"$LOCATION/Sparkle.framework/Versions/A\"\n"; + shellScript = "LINK_LOCATION=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/MacOS/org.eyebeam.SelfControl\"\n\n# Usually set by Xcode\nCODE_SIGN_IDENTITY=\"Developer ID Application: Charlie Stigler (L6W5L88KN7)\"\n\necho \"link location: \"\necho $LINK_LOCATION\n\n# Symlink selfcontrol CLI to org.eyebeam.SelfControl for legacy usage\nif ! [[ -L \"$LINK_LOCATION\" ]]\nthen\n ln -s \"./selfcontrol-cli\" \"$LINK_LOCATION\"\nfi\n\n# codesign --verbose --force --sign \"$CODE_SIGN_IDENTITY\" \"$LOCATION/Sparkle.framework/Versions/A\"\n"; + }; + CB74D1062480E506002B2079 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"#define SELFCONTROL_VERSION_STRING @\\\"${MARKETING_VERSION}\\\"\" > \"${PROJECT_DIR}/version-header.h\"\n"; + }; + CB81A92E25B7B4B8006956F7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\necho \"#define SELFCONTROL_VERSION_STRING @\\\"${MARKETING_VERSION}\\\"\" > \"${PROJECT_DIR}/version-header.h\"\n"; }; - CBFF218A3108765AAB5DE542 /* [CP] Copy Pods Resources */ = { + FFF74CE7FF453369A49FFE5B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-resources.sh", - "${PODS_ROOT}/FormatterKit/Localizations/ca.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/cs.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/da.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/de.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/el.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/en.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/es.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/fr.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/id.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/it.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/ja.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/ko.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/nb.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/nl.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/nn.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/pl.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/pt.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/pt_BR.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/ru.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/sv.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/tr.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/uk.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/vi.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/zh-Hans.lproj", - "${PODS_ROOT}/FormatterKit/Localizations/zh-Hant.lproj", - "${PODS_ROOT}/LetsMove/ca.lproj", - "${PODS_ROOT}/LetsMove/cs.lproj", - "${PODS_ROOT}/LetsMove/da.lproj", - "${PODS_ROOT}/LetsMove/de.lproj", - "${PODS_ROOT}/LetsMove/en.lproj", - "${PODS_ROOT}/LetsMove/es.lproj", - "${PODS_ROOT}/LetsMove/fr.lproj", - "${PODS_ROOT}/LetsMove/hu.lproj", - "${PODS_ROOT}/LetsMove/it.lproj", - "${PODS_ROOT}/LetsMove/ja.lproj", - "${PODS_ROOT}/LetsMove/ko.lproj", - "${PODS_ROOT}/LetsMove/mk.lproj", - "${PODS_ROOT}/LetsMove/nb.lproj", - "${PODS_ROOT}/LetsMove/nl.lproj", - "${PODS_ROOT}/LetsMove/pl.lproj", - "${PODS_ROOT}/LetsMove/pt.lproj", - "${PODS_ROOT}/LetsMove/pt_BR.lproj", - "${PODS_ROOT}/LetsMove/ru.lproj", - "${PODS_ROOT}/LetsMove/sk.lproj", - "${PODS_ROOT}/LetsMove/sr.lproj", - "${PODS_ROOT}/LetsMove/sv.lproj", - "${PODS_ROOT}/LetsMove/tr.lproj", - "${PODS_ROOT}/LetsMove/zh_CN.lproj", - "${PODS_ROOT}/LetsMove/zh_TW.lproj", - "${PODS_ROOT}/MASPreferences/Framework/en.lproj/MASPreferencesWindow.xib", - ); - name = "[CP] Copy Pods Resources"; + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ca.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/cs.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/da.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/de.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/el.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/en.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/es.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/fr.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/id.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/it.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ja.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ko.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nb.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nl.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nn.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pl.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pt.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pt_BR.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ru.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sv.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/tr.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/uk.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/vi.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh-Hans.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh-Hant.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/hu.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/mk.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sk.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sr.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh_CN.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh_TW.lproj", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MASPreferencesWindow.nib", + "$(DERIVED_FILE_DIR)/Pods-SCKillerHelper-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1030,17 +1309,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CBADC27E25B22BC7000EE5BB /* SCSentry.m in Sources */, 8D11072D0486CEB800E47090 /* main.m in Sources */, + CB1CA66525ABA6240084A551 /* SCXPCAuthorization.m in Sources */, CB529BBF0F32B7ED00564FB8 /* AppController.m in Sources */, - CBB1731920F05C07007FCAE9 /* SCUtilities.m in Sources */, + CB81A94925B7B5B5006956F7 /* SCMigrationUtilities.m in Sources */, + CB81A9F325B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, + CB1465B825B027E700130D2E /* SCErr.m in Sources */, + CBB1731920F05C07007FCAE9 /* SCMiscUtilities.m in Sources */, + CB58948025B3FC6D00E9A5C0 /* HostFileBlocker.m in Sources */, F5B8CBEE19EE21C30026F3A5 /* SCTimeIntervalFormatter.m in Sources */, + CB81AB8A25B8E6BE006956F7 /* SCBlockEntry.m in Sources */, CBD4848A19D764440020F949 /* PreferencesGeneralViewController.m in Sources */, + CB1CA65E25ABA6150084A551 /* SCXPCClient.m in Sources */, CBEE50C10F48C21F00F5DF1C /* TimerWindowController.m in Sources */, CB90BF830F49F430006D202D /* HostImporter.m in Sources */, CB5DFCB72251DD1F0084CEC2 /* SCConstants.m in Sources */, CBE4401B0F4BE0670062A1FE /* ThunderbirdPreferenceParser.m in Sources */, CBE5C40B0F4D4531003DB900 /* ButtonWithPopupMenu.m in Sources */, CBD4848F19D768C90020F949 /* PreferencesAdvancedViewController.m in Sources */, + CB81A9D025B7C269006956F7 /* SCBlockUtilities.m in Sources */, CBB7DEEA0F53313F00ABF3EA /* DomainListWindowController.m in Sources */, CBF3B574217BADD7006D5F52 /* SCSettings.m in Sources */, CB25806616C237F10059C99A /* NSString+IPAddress.m in Sources */, @@ -1051,10 +1339,45 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CBDF919A225C5A9700358B95 /* SCUtilities.m in Sources */, + CBDF919A225C5A9700358B95 /* SCMiscUtilities.m in Sources */, CB5DFCBC2251DD1F0084CEC2 /* SCConstants.m in Sources */, + CB81AA3F25B7D152006956F7 /* SCHelperToolUtilities.m in Sources */, + CB81A9D425B7C269006956F7 /* SCBlockUtilities.m in Sources */, + CB81A9F725B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, CB114284222CD4F0004B7868 /* SCSettings.m in Sources */, - CB0EEF7820FE49030024D27B /* SCUtilitiesTests.m in Sources */, + CB0EEF7820FE49030024D27B /* SCUtilityTests.m in Sources */, + CB81A94D25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CB74D1072480E506002B2079 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CB74D11F2480E55D002B2079 /* DaemonMain.m in Sources */, + CB850F3925130F5300EE2E2D /* NSString+IPAddress.m in Sources */, + CB62FC4624B132A300ADBC40 /* HostImporter.m in Sources */, + CB62FC4724B132A600ADBC40 /* AllowlistScraper.m in Sources */, + CB62FC4524B1329F00ADBC40 /* ThunderbirdPreferenceParser.m in Sources */, + CB81A9F825B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, + CB74D1202480E566002B2079 /* SCDaemon.m in Sources */, + CBADC28225B22BC7000EE5BB /* SCSentry.m in Sources */, + CB62FC4024B1327D00ADBC40 /* SCSettings.m in Sources */, + CB62FC3F24B1327A00ADBC40 /* SCMiscUtilities.m in Sources */, + CB1465BC25B027E700130D2E /* SCErr.m in Sources */, + CB81AA4025B7D152006956F7 /* SCHelperToolUtilities.m in Sources */, + CB62FC4924B1330700ADBC40 /* LaunchctlHelper.m in Sources */, + CB62FC4824B132B300ADBC40 /* SCConstants.m in Sources */, + CB81A9D525B7C269006956F7 /* SCBlockUtilities.m in Sources */, + CB62FC4424B1329800ADBC40 /* HostFileBlocker.m in Sources */, + CB62FC3E24B1298500ADBC40 /* SCDaemonBlockMethods.m in Sources */, + CB81A94E25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */, + CB8086D424837607004B88BD /* SCDaemonXPC.m in Sources */, + CB1CA65125ABA5BB0084A551 /* SCXPCClient.m in Sources */, + CB81AB8E25B8E6BE006956F7 /* SCBlockEntry.m in Sources */, + CB69C4EF25A3FD8A0030CFCD /* SCXPCAuthorization.m in Sources */, + CB62FC4324B1329500ADBC40 /* PacketFilter.m in Sources */, + CB62FC4224B1329200ADBC40 /* BlockManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1062,11 +1385,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CB81A9D225B7C269006956F7 /* SCBlockUtilities.m in Sources */, CB9C80FF19CFB79700CDCAE1 /* AppDelegate.m in Sources */, CB9C80FC19CFB79700CDCAE1 /* main.m in Sources */, CB5DFCBA2251DD1F0084CEC2 /* SCConstants.m in Sources */, CB32D2AC21902CF800B8CD68 /* SCSettings.m in Sources */, - CBB1731C20F05C0A007FCAE9 /* SCUtilities.m in Sources */, + CB81A9F525B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, + CB58948725B3FC6F00E9A5C0 /* HostFileBlocker.m in Sources */, + CB1465BA25B027E700130D2E /* SCErr.m in Sources */, + CB81AB8C25B8E6BE006956F7 /* SCBlockEntry.m in Sources */, + CBB1731C20F05C0A007FCAE9 /* SCMiscUtilities.m in Sources */, + CBADC28025B22BC7000EE5BB /* SCSentry.m in Sources */, + CB81A94B25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1074,17 +1404,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CBADC28125B22BC7000EE5BB /* SCSentry.m in Sources */, + CB81AB8D25B8E6BE006956F7 /* SCBlockEntry.m in Sources */, CB9C811E19CFBA8500CDCAE1 /* main.m in Sources */, CB5DFCBB2251DD1F0084CEC2 /* SCConstants.m in Sources */, CB9C812719CFBB6400CDCAE1 /* NSString+IPAddress.m in Sources */, CBE44FEB19E50900004E9706 /* AllowlistScraper.m in Sources */, CB9C812619CFBB5E00CDCAE1 /* BlockManager.m in Sources */, - CB9C812519CFBB5500CDCAE1 /* HelperCommon.m in Sources */, + CB81AA3E25B7D152006956F7 /* SCHelperToolUtilities.m in Sources */, CB9C812419CFBB4E00CDCAE1 /* HostFileBlocker.m in Sources */, + CB81A94C25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */, + CB1CA64F25ABA5BB0084A551 /* SCXPCClient.m in Sources */, CB114283222CCF19004B7868 /* SCSettings.m in Sources */, + CB81A9D325B7C269006956F7 /* SCBlockUtilities.m in Sources */, CB9C812319CFBB4400CDCAE1 /* LaunchctlHelper.m in Sources */, + CB1CA64D25ABA5BB0084A551 /* SCXPCAuthorization.m in Sources */, CB9C812219CFBB3800CDCAE1 /* PacketFilter.m in Sources */, - CBB1731B20F05C09007FCAE9 /* SCUtilities.m in Sources */, + CB1465BB25B027E700130D2E /* SCErr.m in Sources */, + CBB1731B20F05C09007FCAE9 /* SCMiscUtilities.m in Sources */, + CB81A9F625B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1092,18 +1430,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CBADC27F25B22BC7000EE5BB /* SCSentry.m in Sources */, + CB1CA64E25ABA5BB0084A551 /* SCXPCAuthorization.m in Sources */, + CB81A9D125B7C269006956F7 /* SCBlockUtilities.m in Sources */, CB20C5D8245699D700B9D749 /* version-header.h in Sources */, - CBA2AFD90F39EC46005AFEBE /* HelperMain.m in Sources */, + CBA2AFD90F39EC46005AFEBE /* cli-main.m in Sources */, CB5DFCB82251DD1F0084CEC2 /* SCConstants.m in Sources */, + CB81AA3C25B7D152006956F7 /* SCHelperToolUtilities.m in Sources */, + CB81A9F425B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, + CB81AB8B25B8E6BE006956F7 /* SCBlockEntry.m in Sources */, CBCA91121960D87300AFD20C /* PacketFilter.m in Sources */, CB73616219E5086A00E0924F /* AllowlistScraper.m in Sources */, CBC2F8580F4672FE00CF2A42 /* LaunchctlHelper.m in Sources */, CBB0AE2A0FA74566006229B3 /* HostFileBlocker.m in Sources */, - CBD266B011ED7D9C00042CD8 /* HelperCommon.m in Sources */, + CB81A94A25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */, CB32D2A921902CB300B8CD68 /* SCSettings.m in Sources */, + CB1CA65025ABA5BB0084A551 /* SCXPCClient.m in Sources */, CB25806216C1FDBE0059C99A /* BlockManager.m in Sources */, CB25806716C237F10059C99A /* NSString+IPAddress.m in Sources */, - CBB1731520F041F4007FCAE9 /* SCUtilities.m in Sources */, + CB1465B925B027E700130D2E /* SCErr.m in Sources */, + CBB1731520F041F4007FCAE9 /* SCMiscUtilities.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1115,21 +1461,6 @@ target = 8D1107260486CEB800E47090 /* SelfControl */; targetProxy = CB0EEF6220FD8CE00024D27B /* PBXContainerItemProxy */; }; - CB9C812E19CFBBA300CDCAE1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = CB9C811A19CFBA8500CDCAE1 /* SCKillerHelper */; - targetProxy = CB9C812D19CFBBA300CDCAE1 /* PBXContainerItemProxy */; - }; - CBE2BB5519D10CBB0077124F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = CB9C811A19CFBA8500CDCAE1 /* SCKillerHelper */; - targetProxy = CBE2BB5419D10CBB0077124F /* PBXContainerItemProxy */; - }; - CBEF43D00F7BB7F6009A9FDF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = CBA2AFD10F39EC12005AFEBE /* org.eyebeam.SelfControl */; - targetProxy = CBEF43CF0F7BB7F6009A9FDF /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1296,18 +1627,18 @@ isa = XCBuildConfiguration; baseConfigurationReference = DBA01017E61D6B843CA3758D /* Pods-SelfControl.debug.xcconfig */; buildSettings = { + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 3.0.3; + CURRENT_PROJECT_VERSION = 399; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L6W5L88KN7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", "$(PROJECT_DIR)", - "$(PROJECT_DIR)/Pods/Sparkle", ); GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; @@ -1316,11 +1647,14 @@ INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MARKETING_VERSION = 3.0.3; + MARKETING_VERSION = 3.9.9; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.${PRODUCT_NAME:identifier}"; PRODUCT_NAME = SelfControl; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; + SWIFT_OBJC_BRIDGING_HEADER = "SelfControl-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -1328,16 +1662,16 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5F489F28028DC38C046B36D3 /* Pods-SelfControl.release.xcconfig */; buildSettings = { + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3.0.3; + CURRENT_PROJECT_VERSION = 399; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L6W5L88KN7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", "$(PROJECT_DIR)", - "$(PROJECT_DIR)/Pods/Sparkle", ); GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -1345,11 +1679,13 @@ INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MARKETING_VERSION = 3.0.3; + MARKETING_VERSION = 3.9.9; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.${PRODUCT_NAME:identifier}"; PRODUCT_NAME = SelfControl; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; + SWIFT_OBJC_BRIDGING_HEADER = "SelfControl-Bridging-Header.h"; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -1375,7 +1711,6 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_ENTITLEMENTS = SelfControl.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1393,8 +1728,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.8; + MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; + OTHER_CODE_SIGN_FLAGS = "-o library,hard,kill,runtime"; PROVISIONING_PROFILE = ""; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = NO; @@ -1424,7 +1760,6 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_ENTITLEMENTS = SelfControl.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1440,8 +1775,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.8; + MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = NO; + OTHER_CODE_SIGN_FLAGS = "-o library,hard,kill,runtime"; PROVISIONING_PROFILE = ""; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = NO; @@ -1471,8 +1807,10 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", + "TESTING=1", "$(inherited)", ); GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -1508,6 +1846,7 @@ DEVELOPMENT_TEAM = ""; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SelfControlTests/Info.plist; @@ -1521,8 +1860,93 @@ }; name = Release; }; + CB74D11B2480E506002B2079 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92E88BFAB5B19695DD021D54 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = L6W5L88KN7; + GCC_DYNAMIC_NO_PIC = NO; + GCC_MODEL_TUNING = G5; + GCC_NO_COMMON_BLOCKS = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; + GCC_VERSION = ""; + INFOPLIST_FILE = "$(SRCROOT)/Daemon/selfcontrold-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MARKETING_VERSION = 3.9.9; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "Daemon/selfcontrold-Info.plist", + "-sectcreate", + __TEXT, + __launchd_plist, + Daemon/org.eyebeam.selfcontrold.plist, + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.eyebeam.selfcontrold; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "org.eyebeam.SelfControl-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + CB74D11C2480E506002B2079 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B1812886BF1D4F103F3E8D1D /* Pods-org.eyebeam.selfcontrold.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = L6W5L88KN7; + GCC_MODEL_TUNING = G5; + GCC_NO_COMMON_BLOCKS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; + GCC_VERSION = ""; + INFOPLIST_FILE = "$(SRCROOT)/Daemon/selfcontrold-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MARKETING_VERSION = 3.9.9; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "Daemon/selfcontrold-Info.plist", + "-sectcreate", + __TEXT, + __launchd_plist, + Daemon/org.eyebeam.selfcontrold.plist, + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.eyebeam.selfcontrold; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "org.eyebeam.SelfControl-Bridging-Header.h"; + ZERO_LINK = NO; + }; + name = Release; + }; CB9C811219CFB79700CDCAE1 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 40D07D2167368D5ED474897D /* Pods-SelfControl Killer.debug.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -1540,12 +1964,13 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -1558,6 +1983,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "SelfControl Killer/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MARKETING_VERSION = "4.0 alpha"; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.selfcontrolapp.SelfControl-Killer"; @@ -1568,6 +1994,7 @@ }; CB9C811319CFB79700CDCAE1 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 51978917EB66684CE8DAD8C7 /* Pods-SelfControl Killer.release.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -1584,11 +2011,12 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = L6W5L88KN7; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -1596,6 +2024,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "SelfControl Killer/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MARKETING_VERSION = "4.0 alpha"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.selfcontrolapp.SelfControl-Killer"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1605,6 +2034,7 @@ }; CB9C812019CFBA8500CDCAE1 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5D0CD260CB742C9423EB8A83 /* Pods-SCKillerHelper.debug.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -1626,6 +2056,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = NO; GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -1647,6 +2078,7 @@ }; CB9C812119CFBA8500CDCAE1 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 04A4C3645D49A017F4B89A0F /* Pods-SCKillerHelper.release.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -1667,6 +2099,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = NO; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -1682,11 +2115,13 @@ }; CBA2AFD40F39EC14005AFEBE /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EA6CDF7FADBBC7B9A7694C8B /* Pods-selfcontrol-cli.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = "Developer ID Application"; COPY_PHASE_STRIP = NO; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L6W5L88KN7; GCC_DYNAMIC_NO_PIC = NO; @@ -1694,17 +2129,14 @@ GCC_NO_COMMON_BLOCKS = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_VERSION = ""; - INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + INFOPLIST_FILE = "$(SRCROOT)/selfcontrol-cli-Info.plist"; + INFOPLIST_PREPROCESS = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - OTHER_LDFLAGS = ( - "-framework", - Foundation, - "-framework", - AppKit, - ); - PRODUCT_NAME = org.eyebeam.SelfControl; + MARKETING_VERSION = 3.9.9; + PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.selfcontrol-cli"; + PRODUCT_NAME = "selfcontrol-cli"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; @@ -1716,26 +2148,25 @@ }; CBA2AFD50F39EC14005AFEBE /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = F2797D75520FA7BABC7C2B07 /* Pods-selfcontrol-cli.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L6W5L88KN7; GCC_MODEL_TUNING = G5; GCC_NO_COMMON_BLOCKS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + GCC_PREFIX_HEADER = SelfControl_Prefix.pch; GCC_VERSION = ""; - INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + INFOPLIST_FILE = "$(SRCROOT)/selfcontrol-cli-Info.plist"; + INFOPLIST_PREPROCESS = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - OTHER_LDFLAGS = ( - "-framework", - Foundation, - "-framework", - AppKit, - ); - PRODUCT_NAME = org.eyebeam.SelfControl; + MARKETING_VERSION = 3.9.9; + PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.selfcontrol-cli"; + PRODUCT_NAME = "selfcontrol-cli"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; @@ -1775,6 +2206,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + CB74D11A2480E506002B2079 /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CB74D11B2480E506002B2079 /* Debug */, + CB74D11C2480E506002B2079 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; CB9C811119CFB79700CDCAE1 /* Build configuration list for PBXNativeTarget "SelfControl Killer" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1793,7 +2233,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - CBA2AFD60F39EC33005AFEBE /* Build configuration list for PBXNativeTarget "org.eyebeam.SelfControl" */ = { + CBA2AFD60F39EC33005AFEBE /* Build configuration list for PBXNativeTarget "selfcontrol-cli" */ = { isa = XCConfigurationList; buildConfigurations = ( CBA2AFD40F39EC14005AFEBE /* Debug */, diff --git a/SelfControl.xcodeproj/xcshareddata/xcschemes/selfcontrold.xcscheme b/SelfControl.xcodeproj/xcshareddata/xcschemes/selfcontrold.xcscheme new file mode 100644 index 00000000..ed26c720 --- /dev/null +++ b/SelfControl.xcodeproj/xcshareddata/xcschemes/selfcontrold.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SelfControlCommon.h b/SelfControlCommon.h deleted file mode 100644 index 5e701eab..00000000 --- a/SelfControlCommon.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// SelfControlCommon.h -// SelfControl -// -// Created by Charlie Stigler on 2/15/09. -// Copyright 2009 Eyebeam. - -// This file is part of SelfControl. -// -// SelfControl is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// This file holds variables, functions, includes, etc. that should be easily -// accessible from many different parts of SelfControl. - -#define SelfControlLegacyLockFilePath @"/etc/SelfControl.lock" -// #define DEBUG 0 diff --git a/SelfControlTests/Info.plist b/SelfControlTests/Info.plist index 6c40a6cd..21b82eea 100644 --- a/SelfControlTests/Info.plist +++ b/SelfControlTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.0 + 4.0 alpha 1 CFBundleVersion - 1 + 399 diff --git a/SelfControlTests/SCUtilitiesTests.m b/SelfControlTests/SCUtilitiesTests.m deleted file mode 100644 index b0ba0da0..00000000 --- a/SelfControlTests/SCUtilitiesTests.m +++ /dev/null @@ -1,205 +0,0 @@ -// -// BlockDateUtilitiesTests.m -// SelfControlTests -// -// Created by Charles Stigler on 17/07/2018. -// - -#import -#import "SCUtilities.h" -#import "SCSettings.h" - -@interface SCUtilitiesTests : XCTestCase - -@end - -// Static dictionaries of block values to test against - -NSDictionary* activeBlockLegacyDict; // Active (started 5 minutes ago, duration 10 min) -NSDictionary* expiredBlockLegacyDict; // Expired (started 10 minutes 10 seconds ago, duration 10 min) -NSDictionary* noBlockLegacyDict; // start date is distantFuture -NSDictionary* noBlockLegacyDict2; // start date is nil -NSDictionary* emptyLegacyDict; // literally an empty dictionary -NSDictionary* futureStartDateLegacyDict; // start date is in the future -NSDictionary* negativeBlockDurationLegacyDict; // block duration is negative -NSDictionary* veryLongBlockLegacyDict; // year-long block, one day in - -@implementation SCUtilitiesTests - -- (NSUserDefaults*)testDefaults { - return [[NSUserDefaults alloc] initWithSuiteName: @"BlockDateUtilitiesTests"]; -} - -+ (void)setUp { - // Initialize the sample legacy setting dictionaries - activeBlockLegacyDict = @{ - @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -300], // 5 minutes ago - @"BlockDuration": @10 // 10 minutes - }; - expiredBlockLegacyDict = @{ - @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -610], // 10 min 10 seconds ago - @"BlockDuration": @10 // 10 minutes - }; - noBlockLegacyDict = @{ - @"BlockStartedDate": [NSDate distantFuture], - @"BlockDuration": @300 // 6 hours - }; - noBlockLegacyDict2 = @{ - @"BlockDuration": @300 // 6 hours - }; - futureStartDateLegacyDict = @{ - @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: 600], // 10 min from now - @"BlockDuration": @300 // 6 hours - }; - negativeBlockDurationLegacyDict = @{ - @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -600], // 10 min ago - @"BlockDuration": @-15 // negative 15 minutes - }; - veryLongBlockLegacyDict = @{ - @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -86400], // 1 day ago - @"BlockDuration": @432000 // 300 days - }; - emptyLegacyDict = @{ - }; -} - -- (void)setUp { - [super setUp]; - - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void) testCleanBlocklistEntries { - // ignores weird invalid entries - XCTAssert([SCUtilities cleanBlocklistEntry: nil].count == 0); - XCTAssert([SCUtilities cleanBlocklistEntry: @""].count == 0); - XCTAssert([SCUtilities cleanBlocklistEntry: @" "].count == 0); - XCTAssert([SCUtilities cleanBlocklistEntry: @" \n\n \n***!@#$%^*()+=<>,/?| "].count == 0); - XCTAssert([SCUtilities cleanBlocklistEntry: @"://}**"].count == 0); - - // can take a plain hostname - NSArray* cleaned = [SCUtilities cleanBlocklistEntry: @"selfcontrolapp.com"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"selfcontrolapp.com"]); - - // and lowercase it - cleaned = [SCUtilities cleanBlocklistEntry: @"selFconTROLapp.com"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"selfcontrolapp.com"]); - - // with subdomains - cleaned = [SCUtilities cleanBlocklistEntry: @"www.selFconTROLapp.com"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); - - // with http scheme - cleaned = [SCUtilities cleanBlocklistEntry: @"http://www.selFconTROLapp.com"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); - - // with https scheme - cleaned = [SCUtilities cleanBlocklistEntry: @"https://www.selFconTROLapp.com"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); - - // with ftp scheme - cleaned = [SCUtilities cleanBlocklistEntry: @"ftp://www.selFconTROLapp.com"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); - - // with port - cleaned = [SCUtilities cleanBlocklistEntry: @"https://www.selFconTROLapp.com:73"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com:73"]); - - // strips username/password - cleaned = [SCUtilities cleanBlocklistEntry: @"http://charlie:mypass@cnn.com:54"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"cnn.com:54"]); - - // strips path etc - cleaned = [SCUtilities cleanBlocklistEntry: @"http://mysite.com/my/path/is/very/long.php?querystring=ydfjkl&otherquerystring=%40%80%20#cool"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"mysite.com"]); - - // CIDR IP ranges - cleaned = [SCUtilities cleanBlocklistEntry: @"127.0.0.1/20"]; - XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"127.0.0.1/20"]); - - // can split entries by newlines - cleaned = [SCUtilities cleanBlocklistEntry: @"http://charlie:mypass@cnn.com:54\nhttps://selfcontrolAPP.com\n192.168.1.1/24\ntest.com\n{}*&\nhttps://reader.google.com/mypath/is/great.php"]; - XCTAssert(cleaned.count == 5); - XCTAssert([cleaned[0] isEqualToString: @"cnn.com:54"]); - XCTAssert([cleaned[1] isEqualToString: @"selfcontrolapp.com"]); - XCTAssert([cleaned[2] isEqualToString: @"192.168.1.1/24"]); - XCTAssert([cleaned[3] isEqualToString: @"test.com"]); - XCTAssert([cleaned[4] isEqualToString: @"reader.google.com"]); -} - -- (void) testStartingAndRemovingBlocks { - SCSettings* settings = [SCSettings currentUserSettings]; - - XCTAssert(![SCUtilities blockIsRunningInDictionary: settings.dictionaryRepresentation]); - XCTAssert(![SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]); - - // test starting a block - [SCUtilities startBlockInSettings: settings withBlockDuration: 21600]; - XCTAssert([SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]); - NSTimeInterval timeToBlockEnd = [[settings valueForKey: @"BlockEndDate"] timeIntervalSinceNow]; - XCTAssert(round(timeToBlockEnd) == 21600); - - // test removing a block - [SCUtilities removeBlockFromSettings: settings]; - XCTAssert(![SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]); -} -- (void) testModernBlockDetection { - SCSettings* settings = [SCSettings currentUserSettings]; - - XCTAssert(![SCUtilities blockIsRunningInDictionary: settings.dictionaryRepresentation]); - XCTAssert(![SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]); - - // test starting a block - [SCUtilities startBlockInSettings: settings withBlockDuration: 21600]; - XCTAssert(![SCUtilities blockIsRunningInDictionary: settings.dictionaryRepresentation]); - XCTAssert([SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]); - - // turn the block "on" - [settings setValue: @YES forKey: @"BlockIsRunning"]; - XCTAssert([SCUtilities blockIsRunningInDictionary: settings.dictionaryRepresentation]); - XCTAssert([SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]); - - // remove the block - [SCUtilities removeBlockFromSettings: settings]; - XCTAssert(![SCUtilities blockIsRunningInDictionary: settings.dictionaryRepresentation]); - XCTAssert(![SCUtilities blockShouldBeRunningInDictionary: settings.dictionaryRepresentation]); -} - -- (void) testLegacyBlockDetection { - // test blockIsRunningInLegacyDictionary - // the block is "running" even if it's expired, since it hasn't been removed - XCTAssert([SCUtilities blockIsRunningInLegacyDictionary: activeBlockLegacyDict]); - XCTAssert([SCUtilities blockIsRunningInLegacyDictionary: expiredBlockLegacyDict]); - XCTAssert(![SCUtilities blockIsRunningInLegacyDictionary: noBlockLegacyDict]); - XCTAssert(![SCUtilities blockIsRunningInLegacyDictionary: noBlockLegacyDict2]); - XCTAssert([SCUtilities blockIsRunningInLegacyDictionary: futureStartDateLegacyDict]); - XCTAssert([SCUtilities blockIsRunningInLegacyDictionary: negativeBlockDurationLegacyDict]); // negative still might be running? - XCTAssert([SCUtilities blockIsRunningInLegacyDictionary: veryLongBlockLegacyDict]); - XCTAssert(![SCUtilities blockIsRunningInLegacyDictionary: emptyLegacyDict]); - - // test endDateFromLegacyBlockDictionary - NSDate* activeBlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: activeBlockLegacyDict]; - NSDate* expiredBlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: expiredBlockLegacyDict]; - NSDate* noBlockBlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: noBlockLegacyDict]; - NSDate* noBlock2BlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: noBlockLegacyDict2]; - NSDate* futureStartBlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: futureStartDateLegacyDict]; - NSDate* negativeDurationBlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: negativeBlockDurationLegacyDict]; - NSDate* veryLongBlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: veryLongBlockLegacyDict]; - NSDate* emptyBlockEndDate = [SCUtilities endDateFromLegacyBlockDictionary: emptyLegacyDict]; - - XCTAssert(round([activeBlockEndDate timeIntervalSinceNow]) == 300); // 5 min from now - XCTAssert(round([expiredBlockEndDate timeIntervalSinceNow]) == -10); // 10 seconds ago - XCTAssert([noBlockBlockEndDate isEqualToDate: [NSDate distantPast]]); // no block should be active - XCTAssert([noBlock2BlockEndDate isEqualToDate: [NSDate distantPast]]); // no block should be active - XCTAssert([futureStartBlockEndDate isEqualToDate: [NSDate distantPast]]); // no block should be active - XCTAssert([negativeDurationBlockEndDate isEqualToDate: [NSDate distantPast]]); // no block should be active - XCTAssert(round([veryLongBlockEndDate timeIntervalSinceNow]) == 25833600); // 299 days from now - XCTAssert([emptyBlockEndDate isEqualToDate: [NSDate distantPast]]); // block should be expired -} - -@end diff --git a/SelfControlTests/SCUtilityTests.m b/SelfControlTests/SCUtilityTests.m new file mode 100644 index 00000000..a92caa7b --- /dev/null +++ b/SelfControlTests/SCUtilityTests.m @@ -0,0 +1,180 @@ +// +// BlockDateUtilitiesTests.m +// SelfControlTests +// +// Created by Charles Stigler on 17/07/2018. +// + +#import +#import "SCUtility.h" +#import "SCSentry.h" +#import "SCErr.h" +#import "SCSettings.h" + +@interface SCUtilityTests : XCTestCase + +@end + +// Static dictionaries of block values to test against + +NSDictionary* activeBlockLegacyDict; // Active (started 5 minutes ago, duration 10 min) +NSDictionary* expiredBlockLegacyDict; // Expired (started 10 minutes 10 seconds ago, duration 10 min) +NSDictionary* noBlockLegacyDict; // start date is distantFuture +NSDictionary* noBlockLegacyDict2; // start date is nil +NSDictionary* emptyLegacyDict; // literally an empty dictionary +NSDictionary* futureStartDateLegacyDict; // start date is in the future +NSDictionary* negativeBlockDurationLegacyDict; // block duration is negative +NSDictionary* veryLongBlockLegacyDict; // year-long block, one day in + +@implementation SCUtilityTests + +- (NSUserDefaults*)testDefaults { + return [[NSUserDefaults alloc] initWithSuiteName: @"BlockDateUtilitiesTests"]; +} + ++ (void)setUp { + // SCSettings shouldn't be readOnly during our tests + // so we can test changing values + [SCSettings sharedSettings].readOnly = NO; + + // Initialize the sample legacy setting dictionaries + activeBlockLegacyDict = @{ + @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -300], // 5 minutes ago + @"BlockDuration": @10 // 10 minutes + }; + expiredBlockLegacyDict = @{ + @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -610], // 10 min 10 seconds ago + @"BlockDuration": @10 // 10 minutes + }; + noBlockLegacyDict = @{ + @"BlockStartedDate": [NSDate distantFuture], + @"BlockDuration": @300 // 6 hours + }; + noBlockLegacyDict2 = @{ + @"BlockDuration": @300 // 6 hours + }; + futureStartDateLegacyDict = @{ + @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: 600], // 10 min from now + @"BlockDuration": @300 // 6 hours + }; + negativeBlockDurationLegacyDict = @{ + @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -600], // 10 min ago + @"BlockDuration": @-15 // negative 15 minutes + }; + veryLongBlockLegacyDict = @{ + @"BlockStartedDate": [NSDate dateWithTimeIntervalSinceNow: -86400], // 1 day ago + @"BlockDuration": @432000 // 300 days + }; + emptyLegacyDict = @{ + }; +} + +- (void)setUp { + [super setUp]; + + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void) testCleanBlocklistEntries { + // ignores weird invalid entries + XCTAssert([SCMiscUtilities cleanBlocklistEntry: nil].count == 0); + XCTAssert([SCMiscUtilities cleanBlocklistEntry: @""].count == 0); + XCTAssert([SCMiscUtilities cleanBlocklistEntry: @" "].count == 0); + XCTAssert([SCMiscUtilities cleanBlocklistEntry: @" \n\n \n***!@#$%^*()+=<>,/?| "].count == 0); + XCTAssert([SCMiscUtilities cleanBlocklistEntry: @"://}**"].count == 0); + + // can take a plain hostname + NSArray* cleaned = [SCMiscUtilities cleanBlocklistEntry: @"selfcontrolapp.com"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"selfcontrolapp.com"]); + + // and lowercase it + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"selFconTROLapp.com"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"selfcontrolapp.com"]); + + // with subdomains + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"www.selFconTROLapp.com"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); + + // with http scheme + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"http://www.selFconTROLapp.com"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); + + // with https scheme + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"https://www.selFconTROLapp.com"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); + + // with ftp scheme + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"ftp://www.selFconTROLapp.com"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com"]); + + // with port + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"https://www.selFconTROLapp.com:73"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"www.selfcontrolapp.com:73"]); + + // strips username/password + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"http://charlie:mypass@cnn.com:54"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"cnn.com:54"]); + + // strips path etc + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"http://mysite.com/my/path/is/very/long.php?querystring=ydfjkl&otherquerystring=%40%80%20#cool"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"mysite.com"]); + + // CIDR IP ranges + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"127.0.0.1/20"]; + XCTAssert(cleaned.count == 1 && [[cleaned firstObject] isEqualToString: @"127.0.0.1/20"]); + + // can split entries by newlines + cleaned = [SCMiscUtilities cleanBlocklistEntry: @"http://charlie:mypass@cnn.com:54\nhttps://selfcontrolAPP.com\n192.168.1.1/24\ntest.com\n{}*&\nhttps://reader.google.com/mypath/is/great.php"]; + XCTAssert(cleaned.count == 5); + XCTAssert([cleaned[0] isEqualToString: @"cnn.com:54"]); + XCTAssert([cleaned[1] isEqualToString: @"selfcontrolapp.com"]); + XCTAssert([cleaned[2] isEqualToString: @"192.168.1.1/24"]); + XCTAssert([cleaned[3] isEqualToString: @"test.com"]); + XCTAssert([cleaned[4] isEqualToString: @"reader.google.com"]); +} + +- (void) testModernBlockDetection { + SCSettings* settings = [SCSettings sharedSettings]; + + XCTAssert(![SCBlockUtilities modernBlockIsRunning]); + XCTAssert([SCBlockUtilities currentBlockIsExpired]); + + // test a block that should have expired 5 minutes ago + [settings setValue: @YES forKey: @"BlockIsRunning"]; + [settings setValue: @[ @"facebook.com", @"reddit.com" ] forKey: @"ActiveBlocklist"]; + [settings setValue: @NO forKey: @"ActiveBlockAsWhitelist"]; + [settings setValue: [NSDate dateWithTimeIntervalSinceNow: -300] forKey: @"BlockEndDate"]; + + XCTAssert([SCBlockUtilities modernBlockIsRunning]); + XCTAssert([SCBlockUtilities currentBlockIsExpired]); + + // test block that should still be running + [settings setValue: [NSDate dateWithTimeIntervalSinceNow: 300] forKey: @"BlockEndDate"]; + XCTAssert([SCBlockUtilities modernBlockIsRunning]); + XCTAssert(![SCBlockUtilities currentBlockIsExpired]); + + // test removing a block + [SCBlockUtilities removeBlockFromSettings]; + XCTAssert(![SCBlockUtilities modernBlockIsRunning]); + XCTAssert([SCBlockUtilities currentBlockIsExpired]); +} + +- (void) testLegacyBlockDetection { + // test blockIsRunningInLegacyDictionary + // the block is "running" even if it's expired, since it hasn't been removed + XCTAssert([SCMigrationUtilities blockIsRunningInLegacyDictionary: activeBlockLegacyDict]); + XCTAssert([SCMigrationUtilities blockIsRunningInLegacyDictionary: expiredBlockLegacyDict]); + XCTAssert(![SCMigrationUtilities blockIsRunningInLegacyDictionary: noBlockLegacyDict]); + XCTAssert(![SCMigrationUtilities blockIsRunningInLegacyDictionary: noBlockLegacyDict2]); + XCTAssert([SCMigrationUtilities blockIsRunningInLegacyDictionary: futureStartDateLegacyDict]); + XCTAssert([SCMigrationUtilities blockIsRunningInLegacyDictionary: negativeBlockDurationLegacyDict]); // negative still might be running? + XCTAssert([SCMigrationUtilities blockIsRunningInLegacyDictionary: veryLongBlockLegacyDict]); + XCTAssert(![SCMigrationUtilities blockIsRunningInLegacyDictionary: emptyLegacyDict]); +} + +@end diff --git a/SelfControl_Prefix.pch b/SelfControl_Prefix.pch index ccb1c302..0af9d99b 100755 --- a/SelfControl_Prefix.pch +++ b/SelfControl_Prefix.pch @@ -3,6 +3,16 @@ // #ifdef __OBJC__ + #import #import + +#import "version-header.h" +#import "DeprecationSilencers.h" +#import "SCUtility.h" +#import "SCErr.h" +#import "SCConstants.h" +#import "SCSentry.h" +#import "SCSettings.h" + #endif diff --git a/Sparkle.framework/Headers b/Sparkle.framework/Headers new file mode 120000 index 00000000..a177d2a6 --- /dev/null +++ b/Sparkle.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/Sparkle.framework/Modules b/Sparkle.framework/Modules new file mode 120000 index 00000000..5736f318 --- /dev/null +++ b/Sparkle.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/Sparkle.framework/PrivateHeaders b/Sparkle.framework/PrivateHeaders new file mode 120000 index 00000000..d8e56452 --- /dev/null +++ b/Sparkle.framework/PrivateHeaders @@ -0,0 +1 @@ +Versions/Current/PrivateHeaders \ No newline at end of file diff --git a/Sparkle.framework/Resources b/Sparkle.framework/Resources new file mode 120000 index 00000000..953ee36f --- /dev/null +++ b/Sparkle.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/Sparkle.framework/Sparkle b/Sparkle.framework/Sparkle new file mode 120000 index 00000000..b2c52731 --- /dev/null +++ b/Sparkle.framework/Sparkle @@ -0,0 +1 @@ +Versions/Current/Sparkle \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Headers/SPUDownloadData.h b/Sparkle.framework/Versions/A/Headers/SPUDownloadData.h new file mode 100644 index 00000000..41cd5743 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SPUDownloadData.h @@ -0,0 +1,43 @@ +// +// SPUDownloadData.h +// Sparkle +// +// Created by Mayur Pawashe on 8/10/16. +// Copyright © 2016 Sparkle Project. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif + +#import "SUExport.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! + * A class for containing downloaded data along with some information about it. + */ +SU_EXPORT @interface SPUDownloadData : NSObject + +- (instancetype)initWithData:(NSData *)data textEncodingName:(NSString * _Nullable)textEncodingName MIMEType:(NSString * _Nullable)MIMEType; + +/*! + * The raw data that was downloaded. + */ +@property (nonatomic, readonly) NSData *data; + +/*! + * The IANA charset encoding name if available. Eg: "utf-8" + */ +@property (nonatomic, readonly, nullable, copy) NSString *textEncodingName; + +/*! + * The MIME type if available. Eg: "text/plain" + */ +@property (nonatomic, readonly, nullable, copy) NSString *MIMEType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Headers/SPUDownloader.h b/Sparkle.framework/Versions/A/Headers/SPUDownloader.h new file mode 100644 index 00000000..5eee9bd5 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SPUDownloader.h @@ -0,0 +1,25 @@ +// +// SPUDownloader.h +// Downloader +// +// Created by Mayur Pawashe on 4/1/16. +// Copyright © 2016 Sparkle Project. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SPUDownloaderProtocol.h" + +@protocol SPUDownloaderDelegate; + +// This object implements the protocol which we have defined. It provides the actual behavior for the service. It is 'exported' by the service to make it available to the process hosting the service over an NSXPCConnection. +@interface SPUDownloader : NSObject + +// Due to XPC remote object reasons, this delegate is strongly referenced +// Invoke cleanup when done with this instance +- (instancetype)initWithDelegate:(id )delegate; + +@end diff --git a/Sparkle.framework/Versions/A/Headers/SPUDownloaderDelegate.h b/Sparkle.framework/Versions/A/Headers/SPUDownloaderDelegate.h new file mode 100644 index 00000000..76e7e750 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SPUDownloaderDelegate.h @@ -0,0 +1,38 @@ +// +// SPUDownloaderDelegate.h +// Sparkle +// +// Created by Mayur Pawashe on 4/1/16. +// Copyright © 2016 Sparkle Project. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +@class SPUDownloadData; + +@protocol SPUDownloaderDelegate + +// This is only invoked for persistent downloads +- (void)downloaderDidSetDestinationName:(NSString *)destinationName temporaryDirectory:(NSString *)temporaryDirectory; + +// Under rare cases, this may be called more than once, in which case the current progress should be reset back to 0 +// This is only invoked for persistent downloads +- (void)downloaderDidReceiveExpectedContentLength:(int64_t)expectedContentLength; + +// This is only invoked for persistent downloads +- (void)downloaderDidReceiveDataOfLength:(uint64_t)length; + +// downloadData is nil if this is a persisent download, otherwise it's non-nil if it's a temporary download +- (void)downloaderDidFinishWithTemporaryDownloadData:(SPUDownloadData * _Nullable)downloadData; + +- (void)downloaderDidFailWithError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Headers/SPUDownloaderDeprecated.h b/Sparkle.framework/Versions/A/Headers/SPUDownloaderDeprecated.h new file mode 100644 index 00000000..36302df4 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SPUDownloaderDeprecated.h @@ -0,0 +1,13 @@ +// +// SPUDownloaderDeprecated.h +// Sparkle +// +// Created by Deadpikle on 12/20/17. +// Copyright © 2017 Sparkle Project. All rights reserved. +// + +#import "SPUDownloader.h" + +@interface SPUDownloaderDeprecated : SPUDownloader + +@end diff --git a/Sparkle.framework/Versions/A/Headers/SPUDownloaderProtocol.h b/Sparkle.framework/Versions/A/Headers/SPUDownloaderProtocol.h new file mode 100644 index 00000000..ebe477fe --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SPUDownloaderProtocol.h @@ -0,0 +1,34 @@ +// +// SPUDownloaderProtocol.h +// PersistentDownloader +// +// Created by Mayur Pawashe on 4/1/16. +// Copyright © 2016 Sparkle Project. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +@class SPUURLRequest; + +// The protocol that this service will vend as its API. This header file will also need to be visible to the process hosting the service. +@protocol SPUDownloaderProtocol + +- (void)startPersistentDownloadWithRequest:(SPUURLRequest *)request bundleIdentifier:(NSString *)bundleIdentifier desiredFilename:(NSString *)desiredFilename; + +- (void)startTemporaryDownloadWithRequest:(SPUURLRequest *)request; + +- (void)downloadDidFinish; + +- (void)cleanup; + +- (void)cancel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Headers/SPUDownloaderSession.h b/Sparkle.framework/Versions/A/Headers/SPUDownloaderSession.h new file mode 100644 index 00000000..4bde75aa --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SPUDownloaderSession.h @@ -0,0 +1,20 @@ +// +// SPUDownloaderSession.h +// Sparkle +// +// Created by Deadpikle on 12/20/17. +// Copyright © 2017 Sparkle Project. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SPUDownloader.h" +#import "SPUDownloaderProtocol.h" + +NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0) +@interface SPUDownloaderSession : SPUDownloader + +@end diff --git a/Sparkle.framework/Versions/A/Headers/SPUURLRequest.h b/Sparkle.framework/Versions/A/Headers/SPUURLRequest.h new file mode 100644 index 00000000..69496147 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SPUURLRequest.h @@ -0,0 +1,35 @@ +// +// SPUURLRequest.h +// Sparkle +// +// Created by Mayur Pawashe on 5/19/16. +// Copyright © 2016 Sparkle Project. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +// A class that wraps NSURLRequest and implements NSSecureCoding +// This class exists because NSURLRequest did not support NSSecureCoding in macOS 10.8 +// I have not verified if NSURLRequest in 10.9 implements NSSecureCoding or not +@interface SPUURLRequest : NSObject + +// Creates a new URL request +// Only these properties are currently tracked: +// * URL +// * Cache policy +// * Timeout interval +// * HTTP header fields +// * networkServiceType ++ (instancetype)URLRequestWithRequest:(NSURLRequest *)request; + +@property (nonatomic, readonly) NSURLRequest *request; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Headers/SUAppcast.h b/Sparkle.framework/Versions/A/Headers/SUAppcast.h new file mode 100644 index 00000000..34276b7d --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUAppcast.h @@ -0,0 +1,35 @@ +// +// SUAppcast.h +// Sparkle +// +// Created by Andy Matuschak on 3/12/06. +// Copyright 2006 Andy Matuschak. All rights reserved. +// + +#ifndef SUAPPCAST_H +#define SUAPPCAST_H + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SUExport.h" + +NS_ASSUME_NONNULL_BEGIN + +@class SUAppcastItem; +SU_EXPORT @interface SUAppcast : NSObject + +@property (copy, nullable) NSString *userAgentString; +@property (copy, nullable) NSDictionary *httpHeaders; + +- (void)fetchAppcastFromURL:(NSURL *)url inBackground:(BOOL)bg completionBlock:(void (^)(NSError *_Nullable))err; +- (SUAppcast *)copyWithoutDeltaUpdates; + +@property (readonly, copy, nullable) NSArray *items; +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h b/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h new file mode 100644 index 00000000..1d8b1d01 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h @@ -0,0 +1,54 @@ +// +// SUAppcastItem.h +// Sparkle +// +// Created by Andy Matuschak on 3/12/06. +// Copyright 2006 Andy Matuschak. All rights reserved. +// + +#ifndef SUAPPCASTITEM_H +#define SUAPPCASTITEM_H + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SUExport.h" +@class SUSignatures; + +SU_EXPORT @interface SUAppcastItem : NSObject +@property (copy, readonly) NSString *title; +@property (copy, readonly) NSString *dateString; +@property (copy, readonly) NSDate *date; +@property (copy, readonly) NSString *itemDescription; +@property (strong, readonly) NSURL *releaseNotesURL; +@property (strong, readonly) SUSignatures *signatures; +@property (copy, readonly) NSString *minimumSystemVersion; +@property (copy, readonly) NSString *maximumSystemVersion; +@property (strong, readonly) NSURL *fileURL; +@property (nonatomic, readonly) uint64_t contentLength; +@property (copy, readonly) NSString *versionString; +@property (copy, readonly) NSString *osString; +@property (copy, readonly) NSString *displayVersionString; +@property (copy, readonly) NSDictionary *deltaUpdates; +@property (strong, readonly) NSURL *infoURL; +@property (copy, readonly) NSNumber* phasedRolloutInterval; + +// Initializes with data from a dictionary provided by the RSS class. +- (instancetype)initWithDictionary:(NSDictionary *)dict; +- (instancetype)initWithDictionary:(NSDictionary *)dict failureReason:(NSString **)error; + +@property (getter=isDeltaUpdate, readonly) BOOL deltaUpdate; +@property (getter=isCriticalUpdate, readonly) BOOL criticalUpdate; +@property (getter=isMacOsUpdate, readonly) BOOL macOsUpdate; +@property (getter=isInformationOnlyUpdate, readonly) BOOL informationOnlyUpdate; + +// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions. +@property (readonly, copy) NSDictionary *propertiesDictionary; + +- (NSURL *)infoURL; + +@end + +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUCodeSigningVerifier.h b/Sparkle.framework/Versions/A/Headers/SUCodeSigningVerifier.h new file mode 100644 index 00000000..3756a378 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUCodeSigningVerifier.h @@ -0,0 +1,26 @@ +// +// SUCodeSigningVerifier.h +// Sparkle +// +// Created by Andy Matuschak on 7/5/12. +// +// + +#ifndef SUCODESIGNINGVERIFIER_H +#define SUCODESIGNINGVERIFIER_H + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SUExport.h" + +SU_EXPORT @interface SUCodeSigningVerifier : NSObject ++ (BOOL)codeSignatureAtBundleURL:(NSURL *)oldBundlePath matchesSignatureAtBundleURL:(NSURL *)newBundlePath error:(NSError **)error; ++ (BOOL)codeSignatureIsValidAtBundleURL:(NSURL *)bundlePath error:(NSError **)error; ++ (BOOL)bundleAtURLIsCodeSigned:(NSURL *)bundlePath; ++ (NSDictionary *)codeSignatureInfoAtBundleURL:(NSURL *)bundlePath; +@end + +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUErrors.h b/Sparkle.framework/Versions/A/Headers/SUErrors.h new file mode 100644 index 00000000..4b160c4f --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUErrors.h @@ -0,0 +1,57 @@ +// +// SUErrors.h +// Sparkle +// +// Created by C.W. Betts on 10/13/14. +// Copyright (c) 2014 Sparkle Project. All rights reserved. +// + +#ifndef SUERRORS_H +#define SUERRORS_H + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SUExport.h" + +/** + * Error domain used by Sparkle + */ +SU_EXPORT extern NSString *const SUSparkleErrorDomain; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat" +typedef NS_ENUM(OSStatus, SUError) { + // Appcast phase errors. + SUAppcastParseError = 1000, + SUNoUpdateError = 1001, + SUAppcastError = 1002, + SURunningFromDiskImageError = 1003, + SURunningTranslocated = 1004, + + // Download phase errors. + SUTemporaryDirectoryError = 2000, + SUDownloadError = 2001, + + // Extraction phase errors. + SUUnarchivingError = 3000, + SUSignatureError = 3001, + + // Installation phase errors. + SUFileCopyFailure = 4000, + SUAuthenticationFailure = 4001, + SUMissingUpdateError = 4002, + SUMissingInstallerToolError = 4003, + SURelaunchError = 4004, + SUInstallationError = 4005, + SUDowngradeError = 4006, + SUInstallationCancelledError = 4007, + + // System phase errors + SUSystemPowerOffError = 5000 +}; +#pragma clang diagnostic pop + +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUExport.h b/Sparkle.framework/Versions/A/Headers/SUExport.h new file mode 100644 index 00000000..3e3f8a16 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUExport.h @@ -0,0 +1,18 @@ +// +// SUExport.h +// Sparkle +// +// Created by Jake Petroules on 2014-08-23. +// Copyright (c) 2014 Sparkle Project. All rights reserved. +// + +#ifndef SUEXPORT_H +#define SUEXPORT_H + +#ifdef BUILDING_SPARKLE +#define SU_EXPORT __attribute__((visibility("default"))) +#else +#define SU_EXPORT +#endif + +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUStandardVersionComparator.h b/Sparkle.framework/Versions/A/Headers/SUStandardVersionComparator.h new file mode 100644 index 00000000..ed11921a --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUStandardVersionComparator.h @@ -0,0 +1,52 @@ +// +// SUStandardVersionComparator.h +// Sparkle +// +// Created by Andy Matuschak on 12/21/07. +// Copyright 2007 Andy Matuschak. All rights reserved. +// + +#ifndef SUSTANDARDVERSIONCOMPARATOR_H +#define SUSTANDARDVERSIONCOMPARATOR_H + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SUExport.h" +#import "SUVersionComparisonProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! + Sparkle's default version comparator. + + This comparator is adapted from MacPAD, by Kevin Ballard. + It's "dumb" in that it does essentially string comparison, + in components split by character type. +*/ +SU_EXPORT @interface SUStandardVersionComparator : NSObject + +/*! + Initializes a new instance of the standard version comparator. + */ +- (instancetype)init; + +/*! + Returns a singleton instance of the comparator. + + It is usually preferred to alloc/init new a comparator instead. +*/ ++ (SUStandardVersionComparator *)defaultComparator; + +/*! + Compares version strings through textual analysis. + + See the implementation for more details. +*/ +- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; +@end + +NS_ASSUME_NONNULL_END +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUUpdater.h b/Sparkle.framework/Versions/A/Headers/SUUpdater.h new file mode 100644 index 00000000..d05270f2 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUUpdater.h @@ -0,0 +1,233 @@ +// +// SUUpdater.h +// Sparkle +// +// Created by Andy Matuschak on 1/4/06. +// Copyright 2006 Andy Matuschak. All rights reserved. +// + +#ifndef SUUPDATER_H +#define SUUPDATER_H + +#if __has_feature(modules) +@import Cocoa; +#else +#import +#endif +#import "SUExport.h" +#import "SUVersionComparisonProtocol.h" +#import "SUVersionDisplayProtocol.h" + +@class SUAppcastItem, SUAppcast; + +@protocol SUUpdaterDelegate; + +/*! + The main API in Sparkle for controlling the update mechanism. + + This class is used to configure the update paramters as well as manually + and automatically schedule and control checks for updates. + */ +SU_EXPORT @interface SUUpdater : NSObject + +@property (unsafe_unretained) IBOutlet id delegate; + +/*! + The shared updater for the main bundle. + + This is equivalent to passing [NSBundle mainBundle] to SUUpdater::updaterForBundle: + */ ++ (SUUpdater *)sharedUpdater; + +/*! + The shared updater for a specified bundle. + + If an updater has already been initialized for the provided bundle, that shared instance will be returned. + */ ++ (SUUpdater *)updaterForBundle:(NSBundle *)bundle; + +/*! + Designated initializer for SUUpdater. + + If an updater has already been initialized for the provided bundle, that shared instance will be returned. + */ +- (instancetype)initForBundle:(NSBundle *)bundle; + +/*! + Explicitly checks for updates and displays a progress dialog while doing so. + + This method is meant for a main menu item. + Connect any menu item to this action in Interface Builder, + and Sparkle will check for updates and report back its findings verbosely + when it is invoked. + + This will find updates that the user has opted into skipping. + */ +- (IBAction)checkForUpdates:(id)sender; + +/*! + The menu item validation used for the -checkForUpdates: action + */ +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem; + +/*! + Checks for updates, but does not display any UI unless an update is found. + + This is meant for programmatically initating a check for updates. That is, + it will display no UI unless it actually finds an update, in which case it + proceeds as usual. + + If automatic downloading of updates it turned on and allowed, however, + this will invoke that behavior, and if an update is found, it will be downloaded + in the background silently and will be prepped for installation. + + This will not find updates that the user has opted into skipping. + */ +- (void)checkForUpdatesInBackground; + +/*! + A property indicating whether or not to check for updates automatically. + + Setting this property will persist in the host bundle's user defaults. + The update schedule cycle will be reset in a short delay after the property's new value is set. + This is to allow reverting this property without kicking off a schedule change immediately + */ +@property BOOL automaticallyChecksForUpdates; + +/*! + A property indicating whether or not updates can be automatically downloaded in the background. + + Note that automatic downloading of updates can be disallowed by the developer + or by the user's system if silent updates cannot be done (eg: if they require authentication). + In this case, -automaticallyDownloadsUpdates will return NO regardless of how this property is set. + + Setting this property will persist in the host bundle's user defaults. + */ +@property BOOL automaticallyDownloadsUpdates; + +/*! + A property indicating the current automatic update check interval. + + Setting this property will persist in the host bundle's user defaults. + The update schedule cycle will be reset in a short delay after the property's new value is set. + This is to allow reverting this property without kicking off a schedule change immediately + */ +@property NSTimeInterval updateCheckInterval; + +/*! + Begins a "probing" check for updates which will not actually offer to + update to that version. + + However, the delegate methods + SUUpdaterDelegate::updater:didFindValidUpdate: and + SUUpdaterDelegate::updaterDidNotFindUpdate: will be called, + so you can use that information in your UI. + + Updates that have been skipped by the user will not be found. + */ +- (void)checkForUpdateInformation; + +/*! + The URL of the appcast used to download update information. + + Setting this property will persist in the host bundle's user defaults. + If you don't want persistence, you may want to consider instead implementing + SUUpdaterDelegate::feedURLStringForUpdater: or SUUpdaterDelegate::feedParametersForUpdater:sendingSystemProfile: + + This property must be called on the main thread. + */ +@property (copy) NSURL *feedURL; + +/*! + The host bundle that is being updated. + */ +@property (readonly, strong) NSBundle *hostBundle; + +/*! + The bundle this class (SUUpdater) is loaded into. + */ +@property (strong, readonly) NSBundle *sparkleBundle; + +/*! + The user agent used when checking for updates. + + The default implementation can be overrided. + */ +@property (nonatomic, copy) NSString *userAgentString; + +/*! + The HTTP headers used when checking for updates. + + The keys of this dictionary are HTTP header fields (NSString) and values are corresponding values (NSString) + */ +@property (copy) NSDictionary *httpHeaders; + +/*! + A property indicating whether or not the user's system profile information is sent when checking for updates. + + Setting this property will persist in the host bundle's user defaults. + */ +@property BOOL sendsSystemProfile; + +/*! + A property indicating the decryption password used for extracting updates shipped as Apple Disk Images (dmg) + */ +@property (nonatomic, copy) NSString *decryptionPassword; + +/*! + This function ignores normal update schedule, ignores user preferences, + and interrupts users with an unwanted immediate app update. + + WARNING: this function should not be used in regular apps. This function + is a user-unfriendly hack only for very special cases, like unstable + rapidly-changing beta builds that would not run correctly if they were + even one day out of date. + + Instead of this function you should set `SUAutomaticallyUpdate` to `YES`, + which will gracefully install updates when the app quits. + + For UI-less/daemon apps that aren't usually quit, instead of this function, + you can use the delegate method + SUUpdaterDelegate::updater:willInstallUpdateOnQuit:immediateInstallationInvocation: + or + SUUpdaterDelegate::updater:willInstallUpdateOnQuit:immediateInstallationBlock: + to immediately start installation when an update was found. + + A progress dialog is shown but the user will never be prompted to read the + release notes. + + This function will cause update to be downloaded twice if automatic updates are + enabled. + + You may want to respond to the userDidCancelDownload delegate method in case + the user clicks the "Cancel" button while the update is downloading. + */ +- (void)installUpdatesIfAvailable; + +/*! + Returns the date of last update check. + + \returns \c nil if no check has been performed. + */ +@property (readonly, copy) NSDate *lastUpdateCheckDate; + +/*! + Appropriately schedules or cancels the update checking timer according to + the preferences for time interval and automatic checks. + + This call does not change the date of the next check, + but only the internal NSTimer. + */ +- (void)resetUpdateCycle; + +/*! + A property indicating whether or not an update is in progress. + + Note this property is not indicative of whether or not user initiated updates can be performed. + Use SUUpdater::validateMenuItem: for that instead. + */ +@property (readonly) BOOL updateInProgress; + +@end + +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUUpdaterDelegate.h b/Sparkle.framework/Versions/A/Headers/SUUpdaterDelegate.h new file mode 100644 index 00000000..ec844d04 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUUpdaterDelegate.h @@ -0,0 +1,352 @@ +// +// SUUpdaterDelegate.h +// Sparkle +// +// Created by Mayur Pawashe on 12/25/16. +// Copyright © 2016 Sparkle Project. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif + +#import "SUExport.h" + +@protocol SUVersionComparison, SUVersionDisplay; +@class SUUpdater, SUAppcast, SUAppcastItem; + +NS_ASSUME_NONNULL_BEGIN + +// ----------------------------------------------------------------------------- +// SUUpdater Notifications for events that might be interesting to more than just the delegate +// The updater will be the notification object +// ----------------------------------------------------------------------------- +SU_EXPORT extern NSString *const SUUpdaterDidFinishLoadingAppCastNotification; +SU_EXPORT extern NSString *const SUUpdaterDidFindValidUpdateNotification; +SU_EXPORT extern NSString *const SUUpdaterDidNotFindUpdateNotification; +SU_EXPORT extern NSString *const SUUpdaterWillRestartNotification; +#define SUUpdaterWillRelaunchApplicationNotification SUUpdaterWillRestartNotification; +#define SUUpdaterWillInstallUpdateNotification SUUpdaterWillRestartNotification; + +// Key for the SUAppcastItem object in the SUUpdaterDidFindValidUpdateNotification userInfo +SU_EXPORT extern NSString *const SUUpdaterAppcastItemNotificationKey; +// Key for the SUAppcast object in the SUUpdaterDidFinishLoadingAppCastNotification userInfo +SU_EXPORT extern NSString *const SUUpdaterAppcastNotificationKey; + +// ----------------------------------------------------------------------------- +// SUUpdater Delegate: +// ----------------------------------------------------------------------------- + +/*! + Provides methods to control the behavior of an SUUpdater object. + */ +@protocol SUUpdaterDelegate +@optional + +/*! + Returns whether to allow Sparkle to pop up. + + For example, this may be used to prevent Sparkle from interrupting a setup assistant. + + \param updater The SUUpdater instance. + */ +- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)updater; + +/*! + Returns additional parameters to append to the appcast URL's query string. + + This is potentially based on whether or not Sparkle will also be sending along the system profile. + + \param updater The SUUpdater instance. + \param sendingProfile Whether the system profile will also be sent. + + \return An array of dictionaries with keys: "key", "value", "displayKey", "displayValue", the latter two being specifically for display to the user. + */ +- (NSArray *> *)feedParametersForUpdater:(SUUpdater *)updater sendingSystemProfile:(BOOL)sendingProfile; + +/*! + Returns a custom appcast URL. + + Override this to dynamically specify the entire URL. + + An alternative may be to use SUUpdaterDelegate::feedParametersForUpdater:sendingSystemProfile: + and let the server handle what kind of feed to provide. + + \param updater The SUUpdater instance. + */ +- (nullable NSString *)feedURLStringForUpdater:(SUUpdater *)updater; + +/*! + Returns whether Sparkle should prompt the user about automatic update checks. + + Use this to override the default behavior. + + \param updater The SUUpdater instance. + */ +- (BOOL)updaterShouldPromptForPermissionToCheckForUpdates:(SUUpdater *)updater; + +/*! + Called after Sparkle has downloaded the appcast from the remote server. + + Implement this if you want to do some special handling with the appcast once it finishes loading. + + \param updater The SUUpdater instance. + \param appcast The appcast that was downloaded from the remote server. + */ +- (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast; + +/*! + Returns the item in the appcast corresponding to the update that should be installed. + + If you're using special logic or extensions in your appcast, + implement this to use your own logic for finding a valid update, if any, + in the given appcast. + + \param appcast The appcast that was downloaded from the remote server. + \param updater The SUUpdater instance. + */ +- (nullable SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forUpdater:(SUUpdater *)updater; + +/*! + Called when a valid update is found by the update driver. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be installed. + */ +- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)item; + +/*! + Called just before the scheduled update driver prompts the user to install an update. + + \param updater The SUUpdater instance. + + \return YES to allow the update prompt to be shown (the default behavior), or NO to suppress it. + */ +- (BOOL)updaterShouldShowUpdateAlertForScheduledUpdate:(SUUpdater *)updater forItem:(SUAppcastItem *)item; + +/*! + Called after the user dismisses the update alert. + + \param updater The SUUpdater instance. + \param permanently YES if the alert will not appear again for this update; NO if it may reappear. + */ +- (void)updater:(SUUpdater *)updater didDismissUpdateAlertPermanently:(BOOL)permanently forItem:(SUAppcastItem *)item; + +/*! + Called when a valid update is not found. + + \param updater The SUUpdater instance. + */ +- (void)updaterDidNotFindUpdate:(SUUpdater *)updater; + +/*! + Called when the user clicks the Skip This Version button. + + \param updater The SUUpdater instance. + */ +- (void)updater:(SUUpdater *)updater userDidSkipThisVersion:(SUAppcastItem *)item; + +/*! + Called immediately before downloading the specified update. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be downloaded. + \param request The mutable URL request that will be used to download the update. + */ +- (void)updater:(SUUpdater *)updater willDownloadUpdate:(SUAppcastItem *)item withRequest:(NSMutableURLRequest *)request; + +/*! + Called immediately after succesfull download of the specified update. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that has been downloaded. + */ +- (void)updater:(SUUpdater *)updater didDownloadUpdate:(SUAppcastItem *)item; + +/*! + Called after the specified update failed to download. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that failed to download. + \param error The error generated by the failed download. + */ +- (void)updater:(SUUpdater *)updater failedToDownloadUpdate:(SUAppcastItem *)item error:(NSError *)error; + +/*! + Called when the user clicks the cancel button while and update is being downloaded. + + \param updater The SUUpdater instance. + */ +- (void)userDidCancelDownload:(SUUpdater *)updater; + +/*! + Called immediately before extracting the specified downloaded update. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be extracted. + */ +- (void)updater:(SUUpdater *)updater willExtractUpdate:(SUAppcastItem *)item; + +/*! + Called immediately after extracting the specified downloaded update. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that has been extracted. + */ +- (void)updater:(SUUpdater *)updater didExtractUpdate:(SUAppcastItem *)item; + +/*! + Called immediately before installing the specified update. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be installed. + */ +- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)item; + +/*! + Returns whether the relaunch should be delayed in order to perform other tasks. + + This is not called if the user didn't relaunch on the previous update, + in that case it will immediately restart. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be installed. + \param invocation The invocation that must be completed with `[invocation invoke]` before continuing with the relaunch. + + \return \c YES to delay the relaunch until \p invocation is invoked. + */ +- (BOOL)updater:(SUUpdater *)updater shouldPostponeRelaunchForUpdate:(SUAppcastItem *)item untilInvoking:(NSInvocation *)invocation; + +/*! + Returns whether the relaunch should be delayed in order to perform other tasks. + + This is not called if the user didn't relaunch on the previous update, + in that case it will immediately restart. + + This method acts as a simpler alternative to SUUpdaterDelegate::updater:shouldPostponeRelaunchForUpdate:untilInvoking: avoiding usage of NSInvocation, which is not available in Swift environments. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be installed. + + \return \c YES to delay the relaunch. + */ +- (BOOL)updater:(SUUpdater *)updater shouldPostponeRelaunchForUpdate:(SUAppcastItem *)item; + +/*! + Returns whether the application should be relaunched at all. + + Some apps \b cannot be relaunched under certain circumstances. + This method can be used to explicitly prevent a relaunch. + + \param updater The SUUpdater instance. + */ +- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater; + +/*! + Called immediately before relaunching. + + \param updater The SUUpdater instance. + */ +- (void)updaterWillRelaunchApplication:(SUUpdater *)updater; + +/*! + Called immediately after relaunching. SUUpdater delegate must be set before applicationDidFinishLaunching: to catch this event. + + \param updater The SUUpdater instance. + */ +- (void)updaterDidRelaunchApplication:(SUUpdater *)updater; + +/*! + Returns an object that compares version numbers to determine their arithmetic relation to each other. + + This method allows you to provide a custom version comparator. + If you don't implement this method or return \c nil, + the standard version comparator will be used. + + \sa SUStandardVersionComparator + + \param updater The SUUpdater instance. + */ +- (nullable id)versionComparatorForUpdater:(SUUpdater *)updater; + +/*! + Returns an object that formats version numbers for display to the user. + + If you don't implement this method or return \c nil, + the standard version formatter will be used. + + \sa SUUpdateAlert + + \param updater The SUUpdater instance. + */ +- (nullable id)versionDisplayerForUpdater:(SUUpdater *)updater; + +/*! + Returns the path which is used to relaunch the client after the update is installed. + + The default is the path of the host bundle. + + \param updater The SUUpdater instance. + */ +- (nullable NSString *)pathToRelaunchForUpdater:(SUUpdater *)updater; + +/*! + Called before an updater shows a modal alert window, + to give the host the opportunity to hide attached windows that may get in the way. + + \param updater The SUUpdater instance. + */ +- (void)updaterWillShowModalAlert:(SUUpdater *)updater; + +/*! + Called after an updater shows a modal alert window, + to give the host the opportunity to hide attached windows that may get in the way. + + \param updater The SUUpdater instance. + */ +- (void)updaterDidShowModalAlert:(SUUpdater *)updater; + +/*! + Called when an update is scheduled to be silently installed on quit. + This is after an update has been automatically downloaded in the background. + (i.e. SUUpdater::automaticallyDownloadsUpdates is YES) + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be installed. + \param invocation Can be used to trigger an immediate silent install and relaunch. + */ +- (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)item immediateInstallationInvocation:(NSInvocation *)invocation; + +/*! + Called when an update is scheduled to be silently installed on quit. + This is after an update has been automatically downloaded in the background. + (i.e. SUUpdater::automaticallyDownloadsUpdates is YES) + This method acts as a more modern alternative to SUUpdaterDelegate::updater:willInstallUpdateOnQuit:immediateInstallationInvocation: using a block instead of NSInvocation, which is not available in Swift environments. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that is proposed to be installed. + \param installationBlock Can be used to trigger an immediate silent install and relaunch. + */ +- (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)item immediateInstallationBlock:(void (^)(void))installationBlock; + +/*! + Calls after an update that was scheduled to be silently installed on quit has been canceled. + + \param updater The SUUpdater instance. + \param item The appcast item corresponding to the update that was proposed to be installed. + */ +- (void)updater:(SUUpdater *)updater didCancelInstallUpdateOnQuit:(SUAppcastItem *)item; + +/*! + Called after an update is aborted due to an error. + + \param updater The SUUpdater instance. + \param error The error that caused the abort + */ +- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h b/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h new file mode 100644 index 00000000..c654fc4d --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h @@ -0,0 +1,37 @@ +// +// SUVersionComparisonProtocol.h +// Sparkle +// +// Created by Andy Matuschak on 12/21/07. +// Copyright 2007 Andy Matuschak. All rights reserved. +// + +#ifndef SUVERSIONCOMPARISONPROTOCOL_H +#define SUVERSIONCOMPARISONPROTOCOL_H + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SUExport.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! + Provides version comparison facilities for Sparkle. +*/ +@protocol SUVersionComparison + +/*! + An abstract method to compare two version strings. + + Should return NSOrderedAscending if b > a, NSOrderedDescending if b < a, + and NSOrderedSame if they are equivalent. +*/ +- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; // *** MAY BE CALLED ON NON-MAIN THREAD! + +@end + +NS_ASSUME_NONNULL_END +#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h b/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h new file mode 100644 index 00000000..980efb3f --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h @@ -0,0 +1,29 @@ +// +// SUVersionDisplayProtocol.h +// EyeTV +// +// Created by Uli Kusterer on 08.12.09. +// Copyright 2009 Elgato Systems GmbH. All rights reserved. +// + +#if __has_feature(modules) +@import Foundation; +#else +#import +#endif +#import "SUExport.h" + +/*! + Applies special display formatting to version numbers. +*/ +@protocol SUVersionDisplay + +/*! + Formats two version strings. + + Both versions are provided so that important distinguishing information + can be displayed while also leaving out unnecessary/confusing parts. +*/ +- (void)formatVersion:(NSString *_Nonnull*_Nonnull)inOutVersionA andVersion:(NSString *_Nonnull*_Nonnull)inOutVersionB; + +@end diff --git a/Sparkle.framework/Versions/A/Headers/Sparkle.h b/Sparkle.framework/Versions/A/Headers/Sparkle.h new file mode 100644 index 00000000..1085d419 --- /dev/null +++ b/Sparkle.framework/Versions/A/Headers/Sparkle.h @@ -0,0 +1,39 @@ +// +// Sparkle.h +// Sparkle +// +// Created by Andy Matuschak on 3/16/06. (Modified by CDHW on 23/12/07) +// Copyright 2006 Andy Matuschak. All rights reserved. +// + +#ifndef SPARKLE_H +#define SPARKLE_H + +// This list should include the shared headers. It doesn't matter if some of them aren't shared (unless +// there are name-space collisions) so we can list all of them to start with: + +#pragma clang diagnostic push +// Do not use <> style includes since 2.x has two frameworks that need to work: Sparkle and SparkleCore +#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header" + +#import "SUAppcast.h" +#import "SUAppcastItem.h" +#import "SUStandardVersionComparator.h" +#import "SUUpdater.h" +#import "SUUpdaterDelegate.h" +#import "SUVersionComparisonProtocol.h" +#import "SUVersionDisplayProtocol.h" +#import "SUErrors.h" + +#import "SPUDownloader.h" +#import "SPUDownloaderDelegate.h" +#import "SPUDownloaderDeprecated.h" +#import "SPUDownloadData.h" +#import "SPUDownloaderProtocol.h" +#import "SPUDownloaderSession.h" +#import "SPUURLRequest.h" +#import "SUCodeSigningVerifier.h" + +#pragma clang diagnostic pop + +#endif diff --git a/Sparkle.framework/Versions/A/Modules/module.modulemap b/Sparkle.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 00000000..af3fe6d0 --- /dev/null +++ b/Sparkle.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module Sparkle { + umbrella header "Sparkle.h" + + export * + module * { export * } +} diff --git a/Sparkle.framework/Versions/A/PrivateHeaders/SUUnarchiver.h b/Sparkle.framework/Versions/A/PrivateHeaders/SUUnarchiver.h new file mode 100644 index 00000000..a52bf5a2 --- /dev/null +++ b/Sparkle.framework/Versions/A/PrivateHeaders/SUUnarchiver.h @@ -0,0 +1,21 @@ +// +// SUUnarchiver.h +// Sparkle +// +// Created by Andy Matuschak on 3/16/06. +// Copyright 2006 Andy Matuschak. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SUUnarchiverProtocol; + +@interface SUUnarchiver : NSObject + ++ (nullable id )unarchiverForPath:(NSString *)path updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist new file mode 100644 index 00000000..676181f9 --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist @@ -0,0 +1,56 @@ + + + + + BuildMachineOSBuild + 20B28 + CFBundleDevelopmentRegion + English + CFBundleExecutable + Autoupdate + CFBundleIconFile + AppIcon.icns + CFBundleIdentifier + org.sparkle-project.Sparkle.Autoupdate + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.24.0 a-67-g0e162c98 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.24.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12C5020f + DTPlatformName + macosx + DTPlatformVersion + 11.1 + DTSDKBuild + 20C5048g + DTSDKName + macosx11.1 + DTXcode + 1230 + DTXcodeBuild + 12C5020f + LSBackgroundOnly + 1 + LSMinimumSystemVersion + 10.7 + LSUIElement + 1 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate new file mode 100755 index 00000000..164511d4 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/fileop b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/fileop new file mode 100755 index 00000000..29dae8a1 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/fileop differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/PkgInfo b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/PkgInfo new file mode 100644 index 00000000..bd04210f --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/AppIcon.icns b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/AppIcon.icns new file mode 100644 index 00000000..7f2a571c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/AppIcon.icns differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/SUStatus.nib b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/SUStatus.nib new file mode 100644 index 00000000..f9c39a03 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/SUStatus.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings new file mode 100644 index 00000000..e00af341 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings new file mode 100644 index 00000000..f2aea275 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings new file mode 100644 index 00000000..02e077cf Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings new file mode 100644 index 00000000..e0957c60 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings new file mode 100644 index 00000000..202e70b7 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings new file mode 100644 index 00000000..6ef15d4a Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings new file mode 100644 index 00000000..1d70063c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings new file mode 100644 index 00000000..8a083f86 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings new file mode 100644 index 00000000..773f7c99 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings new file mode 100644 index 00000000..954abee0 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings new file mode 100644 index 00000000..ce63fdef Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/hr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/hr.lproj/Sparkle.strings new file mode 100644 index 00000000..ab8fe1a3 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/hr.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/hu.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/hu.lproj/Sparkle.strings new file mode 100644 index 00000000..d30ef64b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/hu.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings new file mode 100644 index 00000000..5f6ace28 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings new file mode 100644 index 00000000..5b4be9ea Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings new file mode 100644 index 00000000..f4685eda Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings new file mode 100644 index 00000000..f008e1ee Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings new file mode 100644 index 00000000..fa4cd97d Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings new file mode 100644 index 00000000..76f3556b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings new file mode 100644 index 00000000..4444f338 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings new file mode 100644 index 00000000..2a7ce299 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings new file mode 100644 index 00000000..18a287e8 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings new file mode 100644 index 00000000..967a4418 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings new file mode 100644 index 00000000..8a11ecf1 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings new file mode 100644 index 00000000..65aa28f2 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings new file mode 100644 index 00000000..caaf0603 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings new file mode 100644 index 00000000..e7c70db7 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings new file mode 100644 index 00000000..058b4ba6 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings new file mode 100644 index 00000000..ffc57672 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings new file mode 100644 index 00000000..263326c9 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings new file mode 100644 index 00000000..71cf325f Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings new file mode 100644 index 00000000..b9517885 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/_CodeSignature/CodeResources b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/_CodeSignature/CodeResources new file mode 100644 index 00000000..bb4125f0 --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/_CodeSignature/CodeResources @@ -0,0 +1,860 @@ + + + + + files + + Resources/AppIcon.icns + + 4McwRDEss5BzWwUMG2Xf93+ze08= + + Resources/SUStatus.nib + + ECVWRExfxyDt5uvKRD+70wc9J6s= + + Resources/ar.lproj/Sparkle.strings + + hash + + Rf4jjdgTqvfw5JO/6f9jHMURv/U= + + optional + + + Resources/ca.lproj/Sparkle.strings + + hash + + wGGx+QzPg/20zZTq7jwCTgf/Ubc= + + optional + + + Resources/cs.lproj/Sparkle.strings + + hash + + bY3rkqi/NJtXtjpK3FbV2o0gxbQ= + + optional + + + Resources/da.lproj/Sparkle.strings + + hash + + 0t7SuLDMBZVsY240PAEsVfH/1qw= + + optional + + + Resources/de.lproj/Sparkle.strings + + hash + + fsC7FJvExHE/2681tuUrjkSF2+A= + + optional + + + Resources/el.lproj/Sparkle.strings + + hash + + NbIN+TRHORCL5Gfj68VRq4KdPXo= + + optional + + + Resources/en.lproj/Sparkle.strings + + hash + + cHZov5FaqzfNhnBo0XdRuTMT4SY= + + optional + + + Resources/es.lproj/Sparkle.strings + + hash + + QPG88BN+x/l2Qk1NLLe3wRa26mQ= + + optional + + + Resources/fi.lproj/Sparkle.strings + + hash + + yd6pIoSj19HMDIUos4Td1Fch7bs= + + optional + + + Resources/fr.lproj/Sparkle.strings + + hash + + X3URilwJPVqMTGbtrYdorODwrMA= + + optional + + + Resources/he.lproj/Sparkle.strings + + hash + + U2WmlYGYmeeIlSW66R8awwmNXIE= + + optional + + + Resources/hr.lproj/Sparkle.strings + + hash + + 7LLOVs76ioMwEDV8Gah+6sV/5No= + + optional + + + Resources/hu.lproj/Sparkle.strings + + hash + + bNEmsO2LyUsMjTESH1I42V9sAOo= + + optional + + + Resources/is.lproj/Sparkle.strings + + hash + + 8fxzD9ZhrvIZVZB1+QSJaPzg80M= + + optional + + + Resources/it.lproj/Sparkle.strings + + hash + + bk1J6vpZjWeUFhBYWuWZf8TDv1A= + + optional + + + Resources/ja.lproj/Sparkle.strings + + hash + + f4EbR/GfMsKeWJ5DN/vhwg/lUoE= + + optional + + + Resources/ko.lproj/Sparkle.strings + + hash + + FRHRQPCWEk9GdJawYTuccg+E2tA= + + optional + + + Resources/nb.lproj/Sparkle.strings + + hash + + sgrDElwUxXtzdw8WaUFWyK3pG9Y= + + optional + + + Resources/nl.lproj/Sparkle.strings + + hash + + PWbC08zHFLROqivY2MAklDh6gkA= + + optional + + + Resources/pl.lproj/Sparkle.strings + + hash + + o7deBXE2Ct8/vQxouej5KkwTcUA= + + optional + + + Resources/pt_BR.lproj/Sparkle.strings + + hash + + /adUv04OXQkCFv+Oed6qktFVQ3E= + + optional + + + Resources/pt_PT.lproj/Sparkle.strings + + hash + + Mji9loJOJvuDY9hz3FhQ4H+HY5E= + + optional + + + Resources/ro.lproj/Sparkle.strings + + hash + + 9U+OTz29kXKZHY/nmvbtemMsB3g= + + optional + + + Resources/ru.lproj/Sparkle.strings + + hash + + VpSLGNvZ6sbRYsF23L8m6TG+P6E= + + optional + + + Resources/sk.lproj/Sparkle.strings + + hash + + qn/mo2EFOyw6keezS64Wo5ZGZXU= + + optional + + + Resources/sl.lproj/Sparkle.strings + + hash + + kwvdisufBenuQzrVg8tYKTX+qgg= + + optional + + + Resources/sv.lproj/Sparkle.strings + + hash + + 98/sk+A2Ew1fmKpuKZ3rq8eS1EM= + + optional + + + Resources/th.lproj/Sparkle.strings + + hash + + HQwGW1Ebf0i+Bl4synks3x2SY2M= + + optional + + + Resources/tr.lproj/Sparkle.strings + + hash + + whUQco5F2wcYdjc+cPKlk+mtx7Q= + + optional + + + Resources/uk.lproj/Sparkle.strings + + hash + + JXhpqvLkX0yDWjbWgsk2wbSObKU= + + optional + + + Resources/zh_CN.lproj/Sparkle.strings + + hash + + OnR96Z9tB0noODRSYssSs63+zGA= + + optional + + + Resources/zh_TW.lproj/Sparkle.strings + + hash + + 1FLKoM5jZ8JGBG/nmyEIA+/aalA= + + optional + + + + files2 + + MacOS/fileop + + cdhash + + JwkAFJqL9xY1mTI+1Kki3oSFsik= + + requirement + cdhash H"d5bc45cc18a448c02d5c4dd6859a64524a5b8a85" or cdhash H"270900149a8bf7163599323ed4a922de8485b229" or cdhash H"1b27242b81a5a51561703e2bb8a5e01acac436e9" or cdhash H"ead0c4c63eafc5d32327f0dbf958b7bd0993ec75" + + Resources/AppIcon.icns + + hash + + 4McwRDEss5BzWwUMG2Xf93+ze08= + + hash2 + + nq7j0ugQwyNbJn/7zGFwxIR0njwU3i7hAYKEyZhvUfE= + + + Resources/SUStatus.nib + + hash + + ECVWRExfxyDt5uvKRD+70wc9J6s= + + hash2 + + AtY9YmPv7cUlbFWP2vCyVdi3/M+XQn98wOlrIES2Dgk= + + + Resources/ar.lproj/Sparkle.strings + + hash + + Rf4jjdgTqvfw5JO/6f9jHMURv/U= + + hash2 + + 2cAJJ5NTxwpRgp24Ca3EuTXfaIIzsYdH3Y9cNCalZfc= + + optional + + + Resources/ca.lproj/Sparkle.strings + + hash + + wGGx+QzPg/20zZTq7jwCTgf/Ubc= + + hash2 + + om5I6jKleuRoCwjfrRRqKWQbs2l8lLj8QGKS47cxybA= + + optional + + + Resources/cs.lproj/Sparkle.strings + + hash + + bY3rkqi/NJtXtjpK3FbV2o0gxbQ= + + hash2 + + RfJgT2b3STcLu71+1iU9ZcSXbfwMWG1EE1C7Wrf3xBk= + + optional + + + Resources/da.lproj/Sparkle.strings + + hash + + 0t7SuLDMBZVsY240PAEsVfH/1qw= + + hash2 + + wu0CpGqE79+TXKIQm+q7ycPTuXhOlwRr/wD5uGHJzLM= + + optional + + + Resources/de.lproj/Sparkle.strings + + hash + + fsC7FJvExHE/2681tuUrjkSF2+A= + + hash2 + + XUpgsFH8KmcbgggpdYbJScCg0tBic9tNLdFh+8cbPyw= + + optional + + + Resources/el.lproj/Sparkle.strings + + hash + + NbIN+TRHORCL5Gfj68VRq4KdPXo= + + hash2 + + wt+2xyusmWAQuJ5kAQlRlvFb1wO4L7/rFdG+VmNjl+Y= + + optional + + + Resources/en.lproj/Sparkle.strings + + hash + + cHZov5FaqzfNhnBo0XdRuTMT4SY= + + hash2 + + 39CdfZZ1CQQz1Gd1+Ukxo2JHl0XESoc/cqWKF091WUk= + + optional + + + Resources/es.lproj/Sparkle.strings + + hash + + QPG88BN+x/l2Qk1NLLe3wRa26mQ= + + hash2 + + mtOoKdoTpGzeTNyzxkVGOMsE0Z3ZZOsmIKDfgA9aj8c= + + optional + + + Resources/fi.lproj/Sparkle.strings + + hash + + yd6pIoSj19HMDIUos4Td1Fch7bs= + + hash2 + + +AiiKWEdH3lesozLJBn3tfK6vi/VSI1/TnWVmIdVVsc= + + optional + + + Resources/fr.lproj/Sparkle.strings + + hash + + X3URilwJPVqMTGbtrYdorODwrMA= + + hash2 + + fyqJl0MhXYRILalxRHpv/JorWLOVLPtNcJioiPtlnYg= + + optional + + + Resources/he.lproj/Sparkle.strings + + hash + + U2WmlYGYmeeIlSW66R8awwmNXIE= + + hash2 + + 4gUlWkwTANV/jd7n4OZoXyT8CAcgWVk/tI3a25wmuLg= + + optional + + + Resources/hr.lproj/Sparkle.strings + + hash + + 7LLOVs76ioMwEDV8Gah+6sV/5No= + + hash2 + + TwklhrooHTXgV6Q9fbvvAB3mPIh7qDbEsNtUzo2fQuU= + + optional + + + Resources/hu.lproj/Sparkle.strings + + hash + + bNEmsO2LyUsMjTESH1I42V9sAOo= + + hash2 + + sRkp8c3Bx1qWdhhSNdOap1PbfmiTziINy1HxGea3SWU= + + optional + + + Resources/is.lproj/Sparkle.strings + + hash + + 8fxzD9ZhrvIZVZB1+QSJaPzg80M= + + hash2 + + xcV1yh/zU3U3TsRUT6vGybvIQitf+ThrogN/uOWmD8k= + + optional + + + Resources/it.lproj/Sparkle.strings + + hash + + bk1J6vpZjWeUFhBYWuWZf8TDv1A= + + hash2 + + Y+caNW+g0mt7HP4JrBxJw+uDwN3j19UYb+q5r9ch4Ow= + + optional + + + Resources/ja.lproj/Sparkle.strings + + hash + + f4EbR/GfMsKeWJ5DN/vhwg/lUoE= + + hash2 + + dSPIvpFbelHRv8liJjN3TUVPbgD1DfhVSGmE+S99quI= + + optional + + + Resources/ko.lproj/Sparkle.strings + + hash + + FRHRQPCWEk9GdJawYTuccg+E2tA= + + hash2 + + +bxn0NPgkxdHLa1MHRT+JRlYmy1jpIuaenpst5RT+RA= + + optional + + + Resources/nb.lproj/Sparkle.strings + + hash + + sgrDElwUxXtzdw8WaUFWyK3pG9Y= + + hash2 + + FG+w+OnLI7nwnNCWiMT50LU98VWj1d08ElfX4k7Ok4w= + + optional + + + Resources/nl.lproj/Sparkle.strings + + hash + + PWbC08zHFLROqivY2MAklDh6gkA= + + hash2 + + xnQkqxaO8zP1xpjY3nyjOd4Fe0gJon2Dbt456ukd/Gw= + + optional + + + Resources/pl.lproj/Sparkle.strings + + hash + + o7deBXE2Ct8/vQxouej5KkwTcUA= + + hash2 + + pDq+41jhfESgJauedrYncFY1O5EMEU3nRyl7mmyYj+s= + + optional + + + Resources/pt_BR.lproj/Sparkle.strings + + hash + + /adUv04OXQkCFv+Oed6qktFVQ3E= + + hash2 + + lY5EZJwPc/Rmfhw1gotkeEKB+ANXqZUlM2G92sZwdJc= + + optional + + + Resources/pt_PT.lproj/Sparkle.strings + + hash + + Mji9loJOJvuDY9hz3FhQ4H+HY5E= + + hash2 + + RUq6VJjn/QyydkNbpklLwfCgRF62+uHhXen2dYLBNuQ= + + optional + + + Resources/ro.lproj/Sparkle.strings + + hash + + 9U+OTz29kXKZHY/nmvbtemMsB3g= + + hash2 + + NNvDsecglQ/utR6YEqxyMj5K976YRWieCIC/PZuWCtQ= + + optional + + + Resources/ru.lproj/Sparkle.strings + + hash + + VpSLGNvZ6sbRYsF23L8m6TG+P6E= + + hash2 + + wJZ5NG+mvj4anRFPUFyvSD0kGrg+ZAqklsPfHuCxLQY= + + optional + + + Resources/sk.lproj/Sparkle.strings + + hash + + qn/mo2EFOyw6keezS64Wo5ZGZXU= + + hash2 + + e3cyzJ87ohC1ff/BzZ5O00MnwRE02U+J1KwXlSZeSSg= + + optional + + + Resources/sl.lproj/Sparkle.strings + + hash + + kwvdisufBenuQzrVg8tYKTX+qgg= + + hash2 + + t8QC+9TBONwKLQvV3fKV0umsnAS8ZDpqPikVksFPtWc= + + optional + + + Resources/sv.lproj/Sparkle.strings + + hash + + 98/sk+A2Ew1fmKpuKZ3rq8eS1EM= + + hash2 + + mJY6aeXFnSx38bF630z5lNPmPtsoYVAwadh0KC+9vfQ= + + optional + + + Resources/th.lproj/Sparkle.strings + + hash + + HQwGW1Ebf0i+Bl4synks3x2SY2M= + + hash2 + + nlP7repbMz6EqHo3sZWnK3tzx47WKSWnULdUHCYPgKk= + + optional + + + Resources/tr.lproj/Sparkle.strings + + hash + + whUQco5F2wcYdjc+cPKlk+mtx7Q= + + hash2 + + xEXUfrylPld+eFGrPyj4wTRPj7vUWOZ2f94sWydq03M= + + optional + + + Resources/uk.lproj/Sparkle.strings + + hash + + JXhpqvLkX0yDWjbWgsk2wbSObKU= + + hash2 + + u0572QZYh6sB0GQdMGMePalOf4zkxE7YQG7pp898SEg= + + optional + + + Resources/zh_CN.lproj/Sparkle.strings + + hash + + OnR96Z9tB0noODRSYssSs63+zGA= + + hash2 + + zvMbFdgVGI0ls9vIRT+sie7dj2g1UjQu7iS+pOgyBo4= + + optional + + + Resources/zh_TW.lproj/Sparkle.strings + + hash + + 1FLKoM5jZ8JGBG/nmyEIA+/aalA= + + hash2 + + Vlf/4QD7/3S0SFqxmTWWcSwtTLWISKUSvLjpgWb7lxQ= + + optional + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Sparkle.framework/Versions/A/Resources/DarkAqua.css b/Sparkle.framework/Versions/A/Resources/DarkAqua.css new file mode 100644 index 00000000..a41e0f28 --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/DarkAqua.css @@ -0,0 +1,9 @@ +html { + color: #FFFFFFD8; +} +:link { + color: #419CFF; +} +:link:active { + color: #FF1919; +} diff --git a/Sparkle.framework/Versions/A/Resources/Info.plist b/Sparkle.framework/Versions/A/Resources/Info.plist new file mode 100644 index 00000000..8786d487 --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,48 @@ + + + + + BuildMachineOSBuild + 20B28 + CFBundleDevelopmentRegion + en + CFBundleExecutable + Sparkle + CFBundleIdentifier + org.sparkle-project.Sparkle + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Sparkle + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.24.0 a-67-g0e162c98 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.24.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12C5020f + DTPlatformName + macosx + DTPlatformVersion + 11.1 + DTSDKBuild + 20C5048g + DTSDKName + macosx11.1 + DTXcode + 1230 + DTXcodeBuild + 12C5020f + LSMinimumSystemVersion + 10.7 + + diff --git a/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist b/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist new file mode 100644 index 00000000..1f75b248 --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist @@ -0,0 +1,314 @@ + + + + + ADP2,1 + Developer Transition Kit + iMac1,1 + iMac G3 (Rev A-D) + iMac4,1 + iMac (Core Duo) + iMac4,2 + iMac for Education (17 inch, Core Duo) + iMac5,1 + iMac (Core 2 Duo, 17 or 20 inch, SuperDrive) + iMac5,2 + iMac (Core 2 Duo, 17 inch, Combo Drive) + iMac6,1 + iMac (Core 2 Duo, 24 inch, SuperDrive) + iMac7,1 + iMac Intel Core 2 Duo (aluminum enclosure) + iMac8,1 + iMac (Core 2 Duo, 20 or 24 inch, Early 2008 ) + iMac9,1 + iMac (Core 2 Duo, 20 or 24 inch, Early or Mid 2009 ) + iMac10,1 + iMac (Core 2 Duo, 21.5 or 27 inch, Late 2009 ) + iMac11,1 + iMac (Core i5 or i7, 27 inch Late 2009) + iMac11,2 + 21.5" iMac (mid 2010) + iMac11,3 + iMac (Core i5 or i7, 27 inch Mid 2010) + iMac12,1 + iMac (Core i3 or i5 or i7, 21.5 inch Mid 2010 or Late 2011) + iMac12,2 + iMac (Core i5 or i7, 27 inch Mid 2011) + iMac13,1 + iMac (Core i3 or i5 or i7, 21.5 inch Late 2012 or Early 2013) + iMac13,2 + iMac (Core i5 or i7, 27 inch Late 2012) + iMac14,1 + iMac (Core i5, 21.5 inch Late 2013) + iMac14,2 + iMac (Core i5 or i7, 27 inch Late 2013) + iMac14,3 + iMac (Core i5 or i7, 21.5 inch Late 2013) + iMac14,4 + iMac (Core i5, 21.5 inch Mid 2014) + iMac15,1 + iMac (Retina 5K Core i5 or i7, 27 inch Late 2014 or Mid 2015) + iMac16,1 + iMac (Core i5, 21,5 inch Late 2015) + iMac16,2 + iMac (Retina 4K Core i5 or i7, 21.5 inch Late 2015) + iMac17,1 + iMac (Retina 5K Core i5 or i7, 27 inch Late 2015) + MacBook1,1 + MacBook (Core Duo) + MacBook2,1 + MacBook (Core 2 Duo) + MacBook4,1 + MacBook (Core 2 Duo Feb 2008) + MacBook5,1 + MacBook (Core 2 Duo, Late 2008, Unibody) + MacBook5,2 + MacBook (Core 2 Duo, Early 2009, White) + MacBook6,1 + MacBook (Core 2 Duo, Late 2009, Unibody) + MacBook7,1 + MacBook (Core 2 Duo, Mid 2010, White) + MacBook8,1 + MacBook (Core M, 12 inch, Early 2015) + MacBookAir1,1 + MacBook Air (Core 2 Duo, 13 inch, Early 2008) + MacBookAir2,1 + MacBook Air (Core 2 Duo, 13 inch, Mid 2009) + MacBookAir3,1 + MacBook Air (Core 2 Duo, 11 inch, Late 2010) + MacBookAir3,2 + MacBook Air (Core 2 Duo, 13 inch, Late 2010) + MacBookAir4,1 + MacBook Air (Core i5 or i7, 11 inch, Mid 2011) + MacBookAir4,2 + MacBook Air (Core i5 or i7, 13 inch, Mid 2011) + MacBookAir5,1 + MacBook Air (Core i5 or i7, 11 inch, Mid 2012) + MacBookAir5,2 + MacBook Air (Core i5 or i7, 13 inch, Mid 2012) + MacBookAir6,1 + MacBook Air (Core i5 or i7, 11 inch, Mid 2013 or Early 2014) + MacBookAir6,2 + MacBook Air (Core i5 or i7, 13 inch, Mid 2013 or Early 2014) + MacBookAir7,1 + MacBook Air (Core i5 or i7, 11 inch, Early 2015) + MacBookAir7,2 + MacBook Air (Core i5 or i7, 13 inch, Early 2015) + MacBookPro1,1 + MacBook Pro Core Duo (15-inch) + MacBookPro1,2 + MacBook Pro Core Duo (17-inch) + MacBookPro2,1 + MacBook Pro Core 2 Duo (17-inch) + MacBookPro2,2 + MacBook Pro Core 2 Duo (15-inch) + MacBookPro3,1 + MacBook Pro Core 2 Duo (15-inch LED, Core 2 Duo) + MacBookPro3,2 + MacBook Pro Core 2 Duo (17-inch HD, Core 2 Duo) + MacBookPro4,1 + MacBook Pro (Core 2 Duo Feb 2008) + MacBookPro5,1 + MacBook Pro Intel Core 2 Duo (aluminum unibody) + MacBookPro5,2 + MacBook Pro Intel Core 2 Duo (aluminum unibody) + MacBookPro5,3 + MacBook Pro Intel Core 2 Duo (aluminum unibody) + MacBookPro5,4 + MacBook Pro Intel Core 2 Duo (aluminum unibody) + MacBookPro5,5 + MacBook Pro Intel Core 2 Duo (aluminum unibody) + MacBookPro6,1 + MacBook Pro Intel Core i5, Intel Core i7 (mid 2010) + MacBookPro6,2 + MacBook Pro Intel Core i5, Intel Core i7 (mid 2010) + MacBookPro7,1 + MacBook Pro Intel Core 2 Duo (mid 2010) + MacBookPro8,1 + MacBook Pro Intel Core i5, Intel Core i7, 13" (early 2011) + MacBookPro8,2 + MacBook Pro Intel Core i7, 15" (early 2011) + MacBookPro8,3 + MacBook Pro Intel Core i7, 17" (early 2011) + MacBookPro9,1 + MacBook Pro (15-inch, Mid 2012) + MacBookPro9,2 + MacBook Pro (13-inch, Mid 2012) + MacBookPro10,1 + MacBook Pro (Retina, Mid 2012) + MacBookPro10,2 + MacBook Pro (Retina, 13-inch, Late 2012) + MacBookPro11,1 + MacBook Pro (Retina, 13-inch, Late 2013) + MacBookPro11,2 + MacBook Pro (Retina, 15-inch, Late 2013) + MacBookPro11,3 + MacBook Pro (Retina, 15-inch, Late 2013) + MacbookPro11,4 + MacBook Pro (Retina, 15-inch, Mid 2015) + MacbookPro11,5 + MacBook Pro (Retina, 15-inch, Mid 2015) + MacbookPro12,1  + MacBook Pro (Retina, 13-inch, Early 2015) + Macmini1,1 + Mac Mini (Core Solo/Duo) + Macmini2,1 + Mac mini Intel Core + Macmini3,1 + Mac mini Intel Core + Macmini4,1 + Mac mini Intel Core (Mid 2010) + Macmini5,1 + Mac mini (Core i5, Mid 2011) + Macmini5,2 + Mac mini (Core i5 or Core i7, Mid 2011) + Macmini5,3 + Mac mini (Core i7, Server, Mid 2011) + Macmini6,1 + Mac mini (Core i5, Late 2012) + Macmini6,2 + Mac mini (Core i7, Normal or Server, Late 2012) + Macmini7,1 + Mac mini (Core i5 or Core i7, Late 2014) + MacPro1,1,Quad + Mac Pro + MacPro1,1 + Mac Pro (four-core) + MacPro2,1 + Mac Pro (eight-core) + MacPro3,1 + Mac Pro (January 2008 4- or 8- core "Harpertown") + MacPro4,1 + Mac Pro (March 2009) + MacPro5,1 + Mac Pro (2010 or 2012) + MacPro6,1 + Mac Pro (Late 2013) + PowerBook1,1 + PowerBook G3 + PowerBook2,1 + iBook G3 + PowerBook2,2 + iBook G3 (FireWire) + PowerBook2,3 + iBook G3 + PowerBook2,4 + iBook G3 + PowerBook3,1 + PowerBook G3 (FireWire) + PowerBook3,2 + PowerBook G4 + PowerBook3,3 + PowerBook G4 (Gigabit Ethernet) + PowerBook3,4 + PowerBook G4 (DVI) + PowerBook3,5 + PowerBook G4 (1GHz / 867MHz) + PowerBook4,1 + iBook G3 (Dual USB, Late 2001) + PowerBook4,2 + iBook G3 (16MB VRAM) + PowerBook4,3 + iBook G3 Opaque 16MB VRAM, 32MB VRAM, Early 2003) + PowerBook5,1 + PowerBook G4 (17 inch) + PowerBook5,2 + PowerBook G4 (15 inch FW 800) + PowerBook5,3 + PowerBook G4 (17-inch 1.33GHz) + PowerBook5,4 + PowerBook G4 (15 inch 1.5/1.33GHz) + PowerBook5,5 + PowerBook G4 (17-inch 1.5GHz) + PowerBook5,6 + PowerBook G4 (15 inch 1.67GHz/1.5GHz) + PowerBook5,7 + PowerBook G4 (17-inch 1.67GHz) + PowerBook5,8 + PowerBook G4 (Double layer SD, 15 inch) + PowerBook5,9 + PowerBook G4 (Double layer SD, 17 inch) + PowerBook6,1 + PowerBook G4 (12 inch) + PowerBook6,2 + PowerBook G4 (12 inch, DVI) + PowerBook6,3 + iBook G4 + PowerBook6,4 + PowerBook G4 (12 inch 1.33GHz) + PowerBook6,5 + iBook G4 (Early-Late 2004) + PowerBook6,7 + iBook G4 (Mid 2005) + PowerBook6,8 + PowerBook G4 (12 inch 1.5GHz) + PowerMac1,1 + Power Macintosh G3 (Blue & White) + PowerMac1,2 + Power Macintosh G4 (PCI Graphics) + PowerMac2,1 + iMac G3 (Slot-loading CD-ROM) + PowerMac2,2 + iMac G3 (Summer 2000) + PowerMac3,1 + Power Macintosh G4 (AGP Graphics) + PowerMac3,2 + Power Macintosh G4 (AGP Graphics) + PowerMac3,3 + Power Macintosh G4 (Gigabit Ethernet) + PowerMac3,4 + Power Macintosh G4 (Digital Audio) + PowerMac3,5 + Power Macintosh G4 (Quick Silver) + PowerMac3,6 + Power Macintosh G4 (Mirrored Drive Door) + PowerMac4,1 + iMac G3 (Early/Summer 2001) + PowerMac4,2 + iMac G4 (Flat Panel) + PowerMac4,4 + eMac + PowerMac4,5 + iMac G4 (17-inch Flat Panel) + PowerMac5,1 + Power Macintosh G4 Cube + PowerMac5,2 + Power Mac G4 Cube + PowerMac6,1 + iMac G4 (USB 2.0) + PowerMac6,3 + iMac G4 (20-inch Flat Panel) + PowerMac6,4 + eMac (USB 2.0, 2005) + PowerMac7,2 + Power Macintosh G5 + PowerMac7,3 + Power Macintosh G5 + PowerMac8,1 + iMac G5 + PowerMac8,2 + iMac G5 (Ambient Light Sensor) + PowerMac9,1 + Power Macintosh G5 (Late 2005) + PowerMac10,1 + Mac Mini G4 + PowerMac10,2 + Mac Mini (Late 2005) + PowerMac11,2 + Power Macintosh G5 (Late 2005) + PowerMac12,1 + iMac G5 (iSight) + RackMac1,1 + Xserve G4 + RackMac1,2 + Xserve G4 (slot-loading, cluster node) + RackMac3,1 + Xserve G5 + Xserve1,1 + Xserve (Intel Xeon) + Xserve2,1 + Xserve (January 2008 quad-core) + Xserve3,1 + Xserve (early 2009) + + diff --git a/Sparkle.framework/Versions/A/Resources/SUStatus.nib b/Sparkle.framework/Versions/A/Resources/SUStatus.nib new file mode 100644 index 00000000..f9c39a03 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/SUStatus.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..65f118ac Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..44b6741d Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..4f215f09 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings new file mode 100644 index 00000000..e00af341 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/ca.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ca.lproj/Sparkle.strings new file mode 100644 index 00000000..f2aea275 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ca.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..103886fe Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..68e44511 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..465e87dd Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings new file mode 100644 index 00000000..02e077cf Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..d22bba56 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..dc1aa3fa Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..3515d02c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings new file mode 100644 index 00000000..e0957c60 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..cf626429 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..25873443 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..37c88052 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings new file mode 100644 index 00000000..202e70b7 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/el.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..ddcb7b3a Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/el.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..0085ef14 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..8668aa87 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/el.lproj/Sparkle.strings new file mode 100644 index 00000000..6ef15d4a Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/el.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..c5732e8f Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..352a9a84 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..8ef47f95 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings new file mode 100644 index 00000000..1d70063c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..01694b25 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..9216cb74 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..49fce208 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings new file mode 100644 index 00000000..8a083f86 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/fi.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/fi.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..86011659 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fi.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/fi.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/fi.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..795d05d3 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fi.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/fi.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/fi.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..5cb6f89c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fi.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/fi.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/fi.lproj/Sparkle.strings new file mode 100644 index 00000000..773f7c99 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fi.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..74c1e5a3 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..dea32c6d Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..06a9205b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings new file mode 100644 index 00000000..954abee0 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/fr_CA.lproj b/Sparkle.framework/Versions/A/Resources/fr_CA.lproj new file mode 120000 index 00000000..f9834a39 --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/fr_CA.lproj @@ -0,0 +1 @@ +fr.lproj \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Resources/he.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/he.lproj/Sparkle.strings new file mode 100644 index 00000000..ce63fdef Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/he.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/hr.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/hr.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..12476cc0 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hr.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/hr.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/hr.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..60659fe7 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hr.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/hr.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/hr.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..3837f8af Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hr.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/hr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/hr.lproj/Sparkle.strings new file mode 100644 index 00000000..ab8fe1a3 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hr.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/hu.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/hu.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..dd46e650 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hu.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/hu.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/hu.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..b2400c73 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hu.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/hu.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/hu.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..a4791c6b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hu.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/hu.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/hu.lproj/Sparkle.strings new file mode 100644 index 00000000..d30ef64b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/hu.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..2e2c6025 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..43990e17 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..00c3b4cf Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings new file mode 100644 index 00000000..5f6ace28 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..fb389d23 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..7eea1a8d Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..966065f6 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings new file mode 100644 index 00000000..5b4be9ea Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..9200a22c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..abe1d0bf Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..6ec0cbdf Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings new file mode 100644 index 00000000..f4685eda Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..b2942676 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..d5e01ade Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..db6622c9 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ko.lproj/Sparkle.strings new file mode 100644 index 00000000..f008e1ee Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ko.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..cbc7a9fd Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..3a875f2b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..9dc8dc71 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/nb.lproj/Sparkle.strings new file mode 100644 index 00000000..fa4cd97d Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nb.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..96e672b3 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..ccec5f8f Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..2b64274f Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings new file mode 100644 index 00000000..76f3556b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..aa19d7e3 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..4fcb4eec Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..aced373c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings new file mode 100644 index 00000000..4444f338 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/pt.lproj b/Sparkle.framework/Versions/A/Resources/pt.lproj new file mode 120000 index 00000000..3c1c9f6d --- /dev/null +++ b/Sparkle.framework/Versions/A/Resources/pt.lproj @@ -0,0 +1 @@ +pt_BR.lproj \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..0e8fdc60 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..0a899437 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..c9bd5783 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings new file mode 100644 index 00000000..2a7ce299 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..7056ce7b Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..d77700f0 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..554151eb Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings new file mode 100644 index 00000000..18a287e8 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..c5907ff1 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..9e45a85c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..56c26903 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings new file mode 100644 index 00000000..967a4418 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..9889fb2d Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..4743f8f9 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..e0d48aa4 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings new file mode 100644 index 00000000..8a11ecf1 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..912d1dd9 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..0aa042e4 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..a7119055 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/sk.lproj/Sparkle.strings new file mode 100644 index 00000000..65aa28f2 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sk.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..74526023 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..96b5964a Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..12fcd8c1 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings new file mode 100644 index 00000000..caaf0603 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..ae7ceb14 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..07518c34 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..6d16cc67 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings new file mode 100644 index 00000000..e7c70db7 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..0ee49a1f Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..5241b1f2 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..d66c6ff0 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings new file mode 100644 index 00000000..058b4ba6 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..24605f17 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..e722b695 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..5c8a7d84 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings new file mode 100644 index 00000000..ffc57672 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..dd540a60 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..8ebec67d Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..dd31811c Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings new file mode 100644 index 00000000..263326c9 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..1cb9d707 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..2c066da9 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..1e5b4eb2 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings new file mode 100644 index 00000000..71cf325f Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib new file mode 100644 index 00000000..3c202810 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib new file mode 100644 index 00000000..27d22201 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib new file mode 100644 index 00000000..79433652 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings new file mode 100644 index 00000000..b9517885 Binary files /dev/null and b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings differ diff --git a/Sparkle.framework/Versions/A/Sparkle b/Sparkle.framework/Versions/A/Sparkle new file mode 100755 index 00000000..a89d9ae9 Binary files /dev/null and b/Sparkle.framework/Versions/A/Sparkle differ diff --git a/Sparkle.framework/Versions/A/_CodeSignature/CodeResources b/Sparkle.framework/Versions/A/_CodeSignature/CodeResources new file mode 100644 index 00000000..beb3429f --- /dev/null +++ b/Sparkle.framework/Versions/A/_CodeSignature/CodeResources @@ -0,0 +1,3900 @@ + + + + + files + + Resources/Autoupdate.app/Contents/Info.plist + + vcMwRErKskmkeyJJVI3Z3sof/sE= + + Resources/Autoupdate.app/Contents/MacOS/Autoupdate + + +dZZmoYTS+EoOzyj5DHipiL7vdA= + + Resources/Autoupdate.app/Contents/MacOS/fileop + + rsEt+sjfXAsfWD9h538L+27pg/s= + + Resources/Autoupdate.app/Contents/PkgInfo + + n57qDP4tZfLD1rCS43W0B4LQjzE= + + Resources/Autoupdate.app/Contents/Resources/AppIcon.icns + + 4McwRDEss5BzWwUMG2Xf93+ze08= + + Resources/Autoupdate.app/Contents/Resources/SUStatus.nib + + ECVWRExfxyDt5uvKRD+70wc9J6s= + + Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings + + hash + + Rf4jjdgTqvfw5JO/6f9jHMURv/U= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings + + hash + + wGGx+QzPg/20zZTq7jwCTgf/Ubc= + + optional + + + Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings + + hash + + bY3rkqi/NJtXtjpK3FbV2o0gxbQ= + + optional + + + Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings + + hash + + 0t7SuLDMBZVsY240PAEsVfH/1qw= + + optional + + + Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings + + hash + + fsC7FJvExHE/2681tuUrjkSF2+A= + + optional + + + Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings + + hash + + NbIN+TRHORCL5Gfj68VRq4KdPXo= + + optional + + + Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings + + hash + + cHZov5FaqzfNhnBo0XdRuTMT4SY= + + optional + + + Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings + + hash + + QPG88BN+x/l2Qk1NLLe3wRa26mQ= + + optional + + + Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings + + hash + + yd6pIoSj19HMDIUos4Td1Fch7bs= + + optional + + + Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings + + hash + + X3URilwJPVqMTGbtrYdorODwrMA= + + optional + + + Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings + + hash + + U2WmlYGYmeeIlSW66R8awwmNXIE= + + optional + + + Resources/Autoupdate.app/Contents/Resources/hr.lproj/Sparkle.strings + + hash + + 7LLOVs76ioMwEDV8Gah+6sV/5No= + + optional + + + Resources/Autoupdate.app/Contents/Resources/hu.lproj/Sparkle.strings + + hash + + bNEmsO2LyUsMjTESH1I42V9sAOo= + + optional + + + Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings + + hash + + 8fxzD9ZhrvIZVZB1+QSJaPzg80M= + + optional + + + Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings + + hash + + bk1J6vpZjWeUFhBYWuWZf8TDv1A= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings + + hash + + f4EbR/GfMsKeWJ5DN/vhwg/lUoE= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings + + hash + + FRHRQPCWEk9GdJawYTuccg+E2tA= + + optional + + + Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings + + hash + + sgrDElwUxXtzdw8WaUFWyK3pG9Y= + + optional + + + Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings + + hash + + PWbC08zHFLROqivY2MAklDh6gkA= + + optional + + + Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings + + hash + + o7deBXE2Ct8/vQxouej5KkwTcUA= + + optional + + + Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings + + hash + + /adUv04OXQkCFv+Oed6qktFVQ3E= + + optional + + + Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings + + hash + + Mji9loJOJvuDY9hz3FhQ4H+HY5E= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings + + hash + + 9U+OTz29kXKZHY/nmvbtemMsB3g= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings + + hash + + VpSLGNvZ6sbRYsF23L8m6TG+P6E= + + optional + + + Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings + + hash + + qn/mo2EFOyw6keezS64Wo5ZGZXU= + + optional + + + Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings + + hash + + kwvdisufBenuQzrVg8tYKTX+qgg= + + optional + + + Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings + + hash + + 98/sk+A2Ew1fmKpuKZ3rq8eS1EM= + + optional + + + Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings + + hash + + HQwGW1Ebf0i+Bl4synks3x2SY2M= + + optional + + + Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings + + hash + + whUQco5F2wcYdjc+cPKlk+mtx7Q= + + optional + + + Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings + + hash + + JXhpqvLkX0yDWjbWgsk2wbSObKU= + + optional + + + Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings + + hash + + OnR96Z9tB0noODRSYssSs63+zGA= + + optional + + + Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings + + hash + + 1FLKoM5jZ8JGBG/nmyEIA+/aalA= + + optional + + + Resources/Autoupdate.app/Contents/_CodeSignature/CodeResources + + VHoMteoopEbbYVG87rxkm0sjkA4= + + Resources/DarkAqua.css + + SCihC2/GG/DhF4xcXD9MYaxhawM= + + Resources/Info.plist + + Vjt5kOIAFdWzUgFgF/aP2v3owEc= + + Resources/SUModelTranslation.plist + + iD2Ex40Usc4ZE6IAhRePqgwK/xw= + + Resources/SUStatus.nib + + ECVWRExfxyDt5uvKRD+70wc9J6s= + + Resources/ar.lproj/SUAutomaticUpdateAlert.nib + + hash + + LtkONVbhTzwCPtbjkr06qSniXCI= + + optional + + + Resources/ar.lproj/SUUpdateAlert.nib + + hash + + JeZDdP1OuZbqkm8UKYiyH00A7ss= + + optional + + + Resources/ar.lproj/SUUpdatePermissionPrompt.nib + + hash + + Heb65H1UseXl7rEaFwVxKauBWnI= + + optional + + + Resources/ar.lproj/Sparkle.strings + + hash + + Rf4jjdgTqvfw5JO/6f9jHMURv/U= + + optional + + + Resources/ca.lproj/Sparkle.strings + + hash + + wGGx+QzPg/20zZTq7jwCTgf/Ubc= + + optional + + + Resources/cs.lproj/SUAutomaticUpdateAlert.nib + + hash + + YpT086oHMS9O2TvSNLZh+39oy80= + + optional + + + Resources/cs.lproj/SUUpdateAlert.nib + + hash + + v2ac1JQZvkm8EHZiTUc/q4aBcU0= + + optional + + + Resources/cs.lproj/SUUpdatePermissionPrompt.nib + + hash + + 2ANG1NY1o8ndm0xcmHwYUvrRk6w= + + optional + + + Resources/cs.lproj/Sparkle.strings + + hash + + bY3rkqi/NJtXtjpK3FbV2o0gxbQ= + + optional + + + Resources/da.lproj/SUAutomaticUpdateAlert.nib + + hash + + XCqcLv38cTpbjAE4zjN/JWeT3+U= + + optional + + + Resources/da.lproj/SUUpdateAlert.nib + + hash + + BY0imp6dA7C0GSOK81VXTJsRccM= + + optional + + + Resources/da.lproj/SUUpdatePermissionPrompt.nib + + hash + + Ev2Nvw9c6bVU5ZF63yVhcyNp84w= + + optional + + + Resources/da.lproj/Sparkle.strings + + hash + + 0t7SuLDMBZVsY240PAEsVfH/1qw= + + optional + + + Resources/de.lproj/SUAutomaticUpdateAlert.nib + + hash + + i/BaQXOSENNulhl0b5jssezuU3Y= + + optional + + + Resources/de.lproj/SUUpdateAlert.nib + + hash + + lieYpCoCaCKAA3EL3/EsBr46vqI= + + optional + + + Resources/de.lproj/SUUpdatePermissionPrompt.nib + + hash + + oy0dHoyKmH2uV/KCHJzCagE+QIE= + + optional + + + Resources/de.lproj/Sparkle.strings + + hash + + fsC7FJvExHE/2681tuUrjkSF2+A= + + optional + + + Resources/el.lproj/SUAutomaticUpdateAlert.nib + + hash + + s3rpfaKP5+1+vGc44qpcWy+h0t8= + + optional + + + Resources/el.lproj/SUUpdateAlert.nib + + hash + + sJcnQqAH4BsB+2rz9riB7iqePh0= + + optional + + + Resources/el.lproj/SUUpdatePermissionPrompt.nib + + hash + + mYyXqqWSoYqVG1zNp1vopIw8r1k= + + optional + + + Resources/el.lproj/Sparkle.strings + + hash + + NbIN+TRHORCL5Gfj68VRq4KdPXo= + + optional + + + Resources/en.lproj/SUAutomaticUpdateAlert.nib + + hash + + Y6bIF/+bAP3t5gBwPcdqxsj4co4= + + optional + + + Resources/en.lproj/SUUpdateAlert.nib + + hash + + L3hoxekBQAtpmyDXNhTX7kRXRtc= + + optional + + + Resources/en.lproj/SUUpdatePermissionPrompt.nib + + hash + + uLKIwoprHw35+b4+/KP/j9X2zVg= + + optional + + + Resources/en.lproj/Sparkle.strings + + hash + + cHZov5FaqzfNhnBo0XdRuTMT4SY= + + optional + + + Resources/es.lproj/SUAutomaticUpdateAlert.nib + + hash + + BoS6NAq1zyVcmkbrKJhcI9Zrezk= + + optional + + + Resources/es.lproj/SUUpdateAlert.nib + + hash + + UMa1QcJf8zfpVUnIZUGFfJ64wTk= + + optional + + + Resources/es.lproj/SUUpdatePermissionPrompt.nib + + hash + + tre2iSm68OK3ztgNotyXuz1MkzI= + + optional + + + Resources/es.lproj/Sparkle.strings + + hash + + QPG88BN+x/l2Qk1NLLe3wRa26mQ= + + optional + + + Resources/fi.lproj/SUAutomaticUpdateAlert.nib + + hash + + 6aUnn3XSgWKnVuYVA/PVSrwora8= + + optional + + + Resources/fi.lproj/SUUpdateAlert.nib + + hash + + k8QjxmBhk5B6v1fGnFkwwX6oakg= + + optional + + + Resources/fi.lproj/SUUpdatePermissionPrompt.nib + + hash + + HB5ASms7UIZfv0WaGh6tCLBEDP8= + + optional + + + Resources/fi.lproj/Sparkle.strings + + hash + + yd6pIoSj19HMDIUos4Td1Fch7bs= + + optional + + + Resources/fr.lproj/SUAutomaticUpdateAlert.nib + + hash + + Nj5v0wIECbpjSTU74xKBngH4DeY= + + optional + + + Resources/fr.lproj/SUUpdateAlert.nib + + hash + + RPgJubd38D/WH1H6B1jSejqILE8= + + optional + + + Resources/fr.lproj/SUUpdatePermissionPrompt.nib + + hash + + cVJfS2Nx3QvdbWEq+tSt8xi9hIg= + + optional + + + Resources/fr.lproj/Sparkle.strings + + hash + + X3URilwJPVqMTGbtrYdorODwrMA= + + optional + + + Resources/he.lproj/Sparkle.strings + + hash + + U2WmlYGYmeeIlSW66R8awwmNXIE= + + optional + + + Resources/hr.lproj/SUAutomaticUpdateAlert.nib + + hash + + SkgPcXJYp5dizLAgiXfyl9EsPoI= + + optional + + + Resources/hr.lproj/SUUpdateAlert.nib + + hash + + 47iMWOA+94RZGJW+QJCeM4xOUsA= + + optional + + + Resources/hr.lproj/SUUpdatePermissionPrompt.nib + + hash + + S2YV0JmEwfPtYsMBBMuvddrPEis= + + optional + + + Resources/hr.lproj/Sparkle.strings + + hash + + 7LLOVs76ioMwEDV8Gah+6sV/5No= + + optional + + + Resources/hu.lproj/SUAutomaticUpdateAlert.nib + + hash + + PfYCBbOThC1gBDzoxD+ijdyQ3T0= + + optional + + + Resources/hu.lproj/SUUpdateAlert.nib + + hash + + 6RlHCvHc9GNh1M7iJhvn12iFGpg= + + optional + + + Resources/hu.lproj/SUUpdatePermissionPrompt.nib + + hash + + 1Yz7vPBCFCly2cHjtbQJPK9PzjE= + + optional + + + Resources/hu.lproj/Sparkle.strings + + hash + + bNEmsO2LyUsMjTESH1I42V9sAOo= + + optional + + + Resources/is.lproj/SUAutomaticUpdateAlert.nib + + hash + + 9eOJ/dQvTMu45Z1UowMPaKHYQOI= + + optional + + + Resources/is.lproj/SUUpdateAlert.nib + + hash + + F0aP96zh7QOSkAdFsBlIzBhmCIg= + + optional + + + Resources/is.lproj/SUUpdatePermissionPrompt.nib + + hash + + xRBgLwOX0xZhrXGjHDHL6S+qCQc= + + optional + + + Resources/is.lproj/Sparkle.strings + + hash + + 8fxzD9ZhrvIZVZB1+QSJaPzg80M= + + optional + + + Resources/it.lproj/SUAutomaticUpdateAlert.nib + + hash + + JXGMS3rtry8HTWEbBibxVCkBEmw= + + optional + + + Resources/it.lproj/SUUpdateAlert.nib + + hash + + W45+n9zTkxt1E3v6cZYLzXNtDlc= + + optional + + + Resources/it.lproj/SUUpdatePermissionPrompt.nib + + hash + + o6d6uYDAajCHTJJOXT7zDECTmIM= + + optional + + + Resources/it.lproj/Sparkle.strings + + hash + + bk1J6vpZjWeUFhBYWuWZf8TDv1A= + + optional + + + Resources/ja.lproj/SUAutomaticUpdateAlert.nib + + hash + + 9fRyIZvZMUaRTaWCda7NsPqJle0= + + optional + + + Resources/ja.lproj/SUUpdateAlert.nib + + hash + + L07PCZt4pHgRzMPxV0N6F2QK9kM= + + optional + + + Resources/ja.lproj/SUUpdatePermissionPrompt.nib + + hash + + b1mgRruuDPTLXfhBEjTV72kV1m0= + + optional + + + Resources/ja.lproj/Sparkle.strings + + hash + + f4EbR/GfMsKeWJ5DN/vhwg/lUoE= + + optional + + + Resources/ko.lproj/SUAutomaticUpdateAlert.nib + + hash + + gi+8llNjjuFSKRkNAA7vN/mf1uA= + + optional + + + Resources/ko.lproj/SUUpdateAlert.nib + + hash + + UNAQQTvtG7+MN/4w4ouu9ZHXfCM= + + optional + + + Resources/ko.lproj/SUUpdatePermissionPrompt.nib + + hash + + ycrHyxLA68Lf8rq4IXBVK62UpGc= + + optional + + + Resources/ko.lproj/Sparkle.strings + + hash + + FRHRQPCWEk9GdJawYTuccg+E2tA= + + optional + + + Resources/nb.lproj/SUAutomaticUpdateAlert.nib + + hash + + kJLypTD4VsCOwsXiOd6700pn0Cc= + + optional + + + Resources/nb.lproj/SUUpdateAlert.nib + + hash + + WIJIO1qR0uNQtJpVkhIarVOfgFw= + + optional + + + Resources/nb.lproj/SUUpdatePermissionPrompt.nib + + hash + + sGjRl91qI6175CwJYuqSYLYylJg= + + optional + + + Resources/nb.lproj/Sparkle.strings + + hash + + sgrDElwUxXtzdw8WaUFWyK3pG9Y= + + optional + + + Resources/nl.lproj/SUAutomaticUpdateAlert.nib + + hash + + wPmqlbK7p5mjPDrezXrYK8CG3NA= + + optional + + + Resources/nl.lproj/SUUpdateAlert.nib + + hash + + 7EXAJEeeN0k32jvOHKr7Icq4644= + + optional + + + Resources/nl.lproj/SUUpdatePermissionPrompt.nib + + hash + + QHLHLZGOJJ4eN75aG1K1VUHNPz8= + + optional + + + Resources/nl.lproj/Sparkle.strings + + hash + + PWbC08zHFLROqivY2MAklDh6gkA= + + optional + + + Resources/pl.lproj/SUAutomaticUpdateAlert.nib + + hash + + 4aIS8LbPLTj63EhS7xEk+qjNzD8= + + optional + + + Resources/pl.lproj/SUUpdateAlert.nib + + hash + + pLII26utl65JwmAFqLiMefDsrGs= + + optional + + + Resources/pl.lproj/SUUpdatePermissionPrompt.nib + + hash + + pZo0pXve6jqBertG5IixAzSpgV4= + + optional + + + Resources/pl.lproj/Sparkle.strings + + hash + + o7deBXE2Ct8/vQxouej5KkwTcUA= + + optional + + + Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib + + hash + + 8fQxJZw+LQiExfyPaqUbNnASsWU= + + optional + + + Resources/pt_BR.lproj/SUUpdateAlert.nib + + hash + + zW5zEa6rDpqLuTDfixuKKE93E5o= + + optional + + + Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib + + hash + + 2dtbduILRWtmfjobyd2yOVhQNH4= + + optional + + + Resources/pt_BR.lproj/Sparkle.strings + + hash + + /adUv04OXQkCFv+Oed6qktFVQ3E= + + optional + + + Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib + + hash + + Svc6e9pHrp1wA7rxw7oKc5HB8qQ= + + optional + + + Resources/pt_PT.lproj/SUUpdateAlert.nib + + hash + + NtD144OppS+BPXNmsReGJoN2Qdo= + + optional + + + Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib + + hash + + 5B8hYUrKag0Unyt6Uk0D2K5opL8= + + optional + + + Resources/pt_PT.lproj/Sparkle.strings + + hash + + Mji9loJOJvuDY9hz3FhQ4H+HY5E= + + optional + + + Resources/ro.lproj/SUAutomaticUpdateAlert.nib + + hash + + 7HEo1dlbwSnit0+4DsAqKDz1jR4= + + optional + + + Resources/ro.lproj/SUUpdateAlert.nib + + hash + + FPWtaRuYrVSPrfAozq/4bSQfMK4= + + optional + + + Resources/ro.lproj/SUUpdatePermissionPrompt.nib + + hash + + GY/ufItfyKYpgw54TfqJlPlymb0= + + optional + + + Resources/ro.lproj/Sparkle.strings + + hash + + 9U+OTz29kXKZHY/nmvbtemMsB3g= + + optional + + + Resources/ru.lproj/SUAutomaticUpdateAlert.nib + + hash + + YHane6xWVhvpJGf5HuoxCyQ/gDs= + + optional + + + Resources/ru.lproj/SUUpdateAlert.nib + + hash + + NxM+W+qAegxK4lKy0uzCclpkVjo= + + optional + + + Resources/ru.lproj/SUUpdatePermissionPrompt.nib + + hash + + AqJRrBMp2yA+umSXxQIQVmpnCN4= + + optional + + + Resources/ru.lproj/Sparkle.strings + + hash + + VpSLGNvZ6sbRYsF23L8m6TG+P6E= + + optional + + + Resources/sk.lproj/SUAutomaticUpdateAlert.nib + + hash + + 5k+e1kFtgoVo77RhyhZSXpRQGOQ= + + optional + + + Resources/sk.lproj/SUUpdateAlert.nib + + hash + + l8XR02tvN10SX2aM9CtZ7BpIcqw= + + optional + + + Resources/sk.lproj/SUUpdatePermissionPrompt.nib + + hash + + burLhfFkzbPjAIqMXw1qKn94xm8= + + optional + + + Resources/sk.lproj/Sparkle.strings + + hash + + qn/mo2EFOyw6keezS64Wo5ZGZXU= + + optional + + + Resources/sl.lproj/SUAutomaticUpdateAlert.nib + + hash + + htsZnsf4S+omnk4Z8En8qOOjrPM= + + optional + + + Resources/sl.lproj/SUUpdateAlert.nib + + hash + + /9xrGwHXOdPKFp82w4OjF+Q0WG8= + + optional + + + Resources/sl.lproj/SUUpdatePermissionPrompt.nib + + hash + + lT25Wn73ZrIgcDJsgzSvIQ97TtY= + + optional + + + Resources/sl.lproj/Sparkle.strings + + hash + + kwvdisufBenuQzrVg8tYKTX+qgg= + + optional + + + Resources/sv.lproj/SUAutomaticUpdateAlert.nib + + hash + + K0QpkucJxh5CRA9TYJCT5+gSHZY= + + optional + + + Resources/sv.lproj/SUUpdateAlert.nib + + hash + + dOxfomMC/X9MFFdsk1MyjKv1yi8= + + optional + + + Resources/sv.lproj/SUUpdatePermissionPrompt.nib + + hash + + W5vyz7ueX3DVKxQC82/3FnvJfeQ= + + optional + + + Resources/sv.lproj/Sparkle.strings + + hash + + 98/sk+A2Ew1fmKpuKZ3rq8eS1EM= + + optional + + + Resources/th.lproj/SUAutomaticUpdateAlert.nib + + hash + + /iyQcDW4+Is40OSlKcWlEe9zKdo= + + optional + + + Resources/th.lproj/SUUpdateAlert.nib + + hash + + cQCWeOMdd6mZEd9k0pl3FrZDT9g= + + optional + + + Resources/th.lproj/SUUpdatePermissionPrompt.nib + + hash + + JIh/Ueyxh8+us+26dxQRmrPiVAE= + + optional + + + Resources/th.lproj/Sparkle.strings + + hash + + HQwGW1Ebf0i+Bl4synks3x2SY2M= + + optional + + + Resources/tr.lproj/SUAutomaticUpdateAlert.nib + + hash + + fjnMF3XlYLFPJSpmIFizoGwOVl8= + + optional + + + Resources/tr.lproj/SUUpdateAlert.nib + + hash + + Y9dabfD0a7F1cV9OuFnyQL5BIIc= + + optional + + + Resources/tr.lproj/SUUpdatePermissionPrompt.nib + + hash + + 5wxy4Op51XjVl1MvUlCnSUfvsj8= + + optional + + + Resources/tr.lproj/Sparkle.strings + + hash + + whUQco5F2wcYdjc+cPKlk+mtx7Q= + + optional + + + Resources/uk.lproj/SUAutomaticUpdateAlert.nib + + hash + + YRRVgJ26NZd9+ebTI3UGdpi35eo= + + optional + + + Resources/uk.lproj/SUUpdateAlert.nib + + hash + + PoeaXUHUKNIm0bkX+GNnvFHlq9w= + + optional + + + Resources/uk.lproj/SUUpdatePermissionPrompt.nib + + hash + + HEubU7VtIHZcWJ6RfdC038Os1gw= + + optional + + + Resources/uk.lproj/Sparkle.strings + + hash + + JXhpqvLkX0yDWjbWgsk2wbSObKU= + + optional + + + Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib + + hash + + 2pvLfCu7EiI6OkCxu3+aLyeTPcU= + + optional + + + Resources/zh_CN.lproj/SUUpdateAlert.nib + + hash + + +xvQE3bFW1QXIUggZBlZkKn0gag= + + optional + + + Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib + + hash + + FT+kQgUNxKGrbheU8uSqkYFSHtI= + + optional + + + Resources/zh_CN.lproj/Sparkle.strings + + hash + + OnR96Z9tB0noODRSYssSs63+zGA= + + optional + + + Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib + + hash + + KQcqbpIyw3yhI0eEyo06cNq5MlM= + + optional + + + Resources/zh_TW.lproj/SUUpdateAlert.nib + + hash + + 6/Rgln3/89vly1RFa1gBfRhITxU= + + optional + + + Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib + + hash + + qO2OAmNcqk2/bSzwAjGcXTD4+PY= + + optional + + + Resources/zh_TW.lproj/Sparkle.strings + + hash + + 1FLKoM5jZ8JGBG/nmyEIA+/aalA= + + optional + + + + files2 + + Headers/SPUDownloadData.h + + hash + + 0uirAEMDYuzqSh3dbtfncvlTpvo= + + hash2 + + DkOXqPnwmXR6QeDI4ja3DtFxDW5L3Dcjf21hEPWImoY= + + + Headers/SPUDownloader.h + + hash + + OAA6tKxz1g4V1SQZaj3RPNmNMzo= + + hash2 + + OEDz5kY4WgtWuZ8SApjyqvVDEf3iEj7Sg2uhl+3J+jk= + + + Headers/SPUDownloaderDelegate.h + + hash + + UkBvHxzNYA/YON0ImE8m/8g4vkg= + + hash2 + + 8D6RyR2aziRQIiRN3e7qxlEM9jfgkwBBTWmSjDhLqm8= + + + Headers/SPUDownloaderDeprecated.h + + hash + + l7/+P82TkgRp/YLnQtJtjMWd3/A= + + hash2 + + A7PBMhwuTH0X/JDeyLlh1Xrdc0krwjPqdBPCiePFrxQ= + + + Headers/SPUDownloaderProtocol.h + + hash + + VsD58zsMmnH4mVuK456ekiJcYFU= + + hash2 + + 02oqvyfRCQp5SMSBQNzYZaz1/0VTso0GKnOSbuDEAVo= + + + Headers/SPUDownloaderSession.h + + hash + + IeDSEpUGRwB9HcDVlqbxb6k19A8= + + hash2 + + ZFXLQz90P2h4rR2v1VqRAKzrSSlItkO5cW/0AFy7eYQ= + + + Headers/SPUURLRequest.h + + hash + + 588/W6GUNXYFVar9nygqHNYeTX8= + + hash2 + + 4EG4qYbJgM0wBJCoSPD1lbbDTLHlm85m4xNehyXyU74= + + + Headers/SUAppcast.h + + hash + + puyTMIscPdgGWGQM4TITePIKSdA= + + hash2 + + R3/VQvZCv8Ab34CC17vtBHR3dqkJXnidTR5n8aX4DD0= + + + Headers/SUAppcastItem.h + + hash + + YQvyEYAn6UWDwU8NT5ccPwhdRXE= + + hash2 + + ZB27X0FfNZ54ez8FZAFDEi6/o4BYtQnl4A9Y3tAbkUI= + + + Headers/SUCodeSigningVerifier.h + + hash + + Wj90pgADRte0DhyUmlOXWORp830= + + hash2 + + PPwqqlfaR8hxIfD2YEKdVzyDN4kBVdul55HDAELxQ7U= + + + Headers/SUErrors.h + + hash + + fXyYJmeBH5lNthmGD2GJOWkMmjg= + + hash2 + + JKSoOJCqip87NB39v1ip2Gki8x1rGVcXB7D6kTCeBTI= + + + Headers/SUExport.h + + hash + + G2w3uU/V7JzIupuOjQ2LyPVGdo4= + + hash2 + + XO8CQmbFThLbYg949NEGhg3g+iouIw3/3+BCCLtEdFE= + + + Headers/SUStandardVersionComparator.h + + hash + + 6L4GrA8i1OjMLIGc/sFvEUeSfso= + + hash2 + + 5HrjOlX+uJHOw1ma+1BtG5tlYx/75N/gVzt+BX/GOxg= + + + Headers/SUUpdater.h + + hash + + krK593R1jTMhVN/yl8nXWo1WIfg= + + hash2 + + IJc5cjKEoEbG6onu/Dy+1OXT5YJ7GV3R1U0u2FamUsY= + + + Headers/SUUpdaterDelegate.h + + hash + + MHw1NrGxR116E8+4fIwJlj7xhlU= + + hash2 + + I9h48457oU5WcQiRBj2su4zWt34+hDtHwnoeLWmZ7Ko= + + + Headers/SUVersionComparisonProtocol.h + + hash + + pin8CrEV9eYWF9sdDu0N1YixWOE= + + hash2 + + rsm3T+GsIhDgSqY8EtkBpIxYgSZCZxf4HE9a/FcTRCc= + + + Headers/SUVersionDisplayProtocol.h + + hash + + D3yVFnts0j1IacN9dSN7iZSVlZg= + + hash2 + + AQITUMp8aq1UAOrmksAKmGFpgI24u9rDSBBZrgDqdN4= + + + Headers/Sparkle.h + + hash + + wr5DylsSwG9e5D1UOhUWQxcyz/g= + + hash2 + + OWWeiknWNtWrmGQq0pZ+Hst0PnbN6WbPhGHqIhk4MOM= + + + Modules/module.modulemap + + hash + + /2jgDcgH5+EG9ojcVskajfVo4yg= + + hash2 + + 1TF+JZkzFr6n8oH4WItto+C5Vf3K12f0H9KjqD0A5QU= + + + PrivateHeaders/SUUnarchiver.h + + hash + + 7mTpmnTkJ97DZQQzK/hdl2aZmXU= + + hash2 + + SQYAanTtlyX15CJapj5tDbhBEMtgQ7ZNdmpSij0+tD4= + + + Resources/Autoupdate.app/Contents/Info.plist + + hash + + vcMwRErKskmkeyJJVI3Z3sof/sE= + + hash2 + + KAQWAU9pYYHyY0ffDykJoZXUPjiewvVWowIwn5qsBNc= + + + Resources/Autoupdate.app/Contents/MacOS/Autoupdate + + hash + + +dZZmoYTS+EoOzyj5DHipiL7vdA= + + hash2 + + /CLwW6G+QiK5ETp+clGZsGSksu5/2ppP1elEQVje7VY= + + + Resources/Autoupdate.app/Contents/MacOS/fileop + + hash + + rsEt+sjfXAsfWD9h538L+27pg/s= + + hash2 + + kI/01mUGzFnWLufOtr6ca07s6VBhqA7INTCKBS4xA+8= + + + Resources/Autoupdate.app/Contents/PkgInfo + + hash + + n57qDP4tZfLD1rCS43W0B4LQjzE= + + hash2 + + glAhkclISwTWhTdPmHmgBmBpxJuKyuegSwHTjQfo7KA= + + + Resources/Autoupdate.app/Contents/Resources/AppIcon.icns + + hash + + 4McwRDEss5BzWwUMG2Xf93+ze08= + + hash2 + + nq7j0ugQwyNbJn/7zGFwxIR0njwU3i7hAYKEyZhvUfE= + + + Resources/Autoupdate.app/Contents/Resources/SUStatus.nib + + hash + + ECVWRExfxyDt5uvKRD+70wc9J6s= + + hash2 + + AtY9YmPv7cUlbFWP2vCyVdi3/M+XQn98wOlrIES2Dgk= + + + Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings + + hash + + Rf4jjdgTqvfw5JO/6f9jHMURv/U= + + hash2 + + 2cAJJ5NTxwpRgp24Ca3EuTXfaIIzsYdH3Y9cNCalZfc= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings + + hash + + wGGx+QzPg/20zZTq7jwCTgf/Ubc= + + hash2 + + om5I6jKleuRoCwjfrRRqKWQbs2l8lLj8QGKS47cxybA= + + optional + + + Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings + + hash + + bY3rkqi/NJtXtjpK3FbV2o0gxbQ= + + hash2 + + RfJgT2b3STcLu71+1iU9ZcSXbfwMWG1EE1C7Wrf3xBk= + + optional + + + Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings + + hash + + 0t7SuLDMBZVsY240PAEsVfH/1qw= + + hash2 + + wu0CpGqE79+TXKIQm+q7ycPTuXhOlwRr/wD5uGHJzLM= + + optional + + + Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings + + hash + + fsC7FJvExHE/2681tuUrjkSF2+A= + + hash2 + + XUpgsFH8KmcbgggpdYbJScCg0tBic9tNLdFh+8cbPyw= + + optional + + + Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings + + hash + + NbIN+TRHORCL5Gfj68VRq4KdPXo= + + hash2 + + wt+2xyusmWAQuJ5kAQlRlvFb1wO4L7/rFdG+VmNjl+Y= + + optional + + + Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings + + hash + + cHZov5FaqzfNhnBo0XdRuTMT4SY= + + hash2 + + 39CdfZZ1CQQz1Gd1+Ukxo2JHl0XESoc/cqWKF091WUk= + + optional + + + Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings + + hash + + QPG88BN+x/l2Qk1NLLe3wRa26mQ= + + hash2 + + mtOoKdoTpGzeTNyzxkVGOMsE0Z3ZZOsmIKDfgA9aj8c= + + optional + + + Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings + + hash + + yd6pIoSj19HMDIUos4Td1Fch7bs= + + hash2 + + +AiiKWEdH3lesozLJBn3tfK6vi/VSI1/TnWVmIdVVsc= + + optional + + + Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings + + hash + + X3URilwJPVqMTGbtrYdorODwrMA= + + hash2 + + fyqJl0MhXYRILalxRHpv/JorWLOVLPtNcJioiPtlnYg= + + optional + + + Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings + + hash + + U2WmlYGYmeeIlSW66R8awwmNXIE= + + hash2 + + 4gUlWkwTANV/jd7n4OZoXyT8CAcgWVk/tI3a25wmuLg= + + optional + + + Resources/Autoupdate.app/Contents/Resources/hr.lproj/Sparkle.strings + + hash + + 7LLOVs76ioMwEDV8Gah+6sV/5No= + + hash2 + + TwklhrooHTXgV6Q9fbvvAB3mPIh7qDbEsNtUzo2fQuU= + + optional + + + Resources/Autoupdate.app/Contents/Resources/hu.lproj/Sparkle.strings + + hash + + bNEmsO2LyUsMjTESH1I42V9sAOo= + + hash2 + + sRkp8c3Bx1qWdhhSNdOap1PbfmiTziINy1HxGea3SWU= + + optional + + + Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings + + hash + + 8fxzD9ZhrvIZVZB1+QSJaPzg80M= + + hash2 + + xcV1yh/zU3U3TsRUT6vGybvIQitf+ThrogN/uOWmD8k= + + optional + + + Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings + + hash + + bk1J6vpZjWeUFhBYWuWZf8TDv1A= + + hash2 + + Y+caNW+g0mt7HP4JrBxJw+uDwN3j19UYb+q5r9ch4Ow= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings + + hash + + f4EbR/GfMsKeWJ5DN/vhwg/lUoE= + + hash2 + + dSPIvpFbelHRv8liJjN3TUVPbgD1DfhVSGmE+S99quI= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings + + hash + + FRHRQPCWEk9GdJawYTuccg+E2tA= + + hash2 + + +bxn0NPgkxdHLa1MHRT+JRlYmy1jpIuaenpst5RT+RA= + + optional + + + Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings + + hash + + sgrDElwUxXtzdw8WaUFWyK3pG9Y= + + hash2 + + FG+w+OnLI7nwnNCWiMT50LU98VWj1d08ElfX4k7Ok4w= + + optional + + + Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings + + hash + + PWbC08zHFLROqivY2MAklDh6gkA= + + hash2 + + xnQkqxaO8zP1xpjY3nyjOd4Fe0gJon2Dbt456ukd/Gw= + + optional + + + Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings + + hash + + o7deBXE2Ct8/vQxouej5KkwTcUA= + + hash2 + + pDq+41jhfESgJauedrYncFY1O5EMEU3nRyl7mmyYj+s= + + optional + + + Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings + + hash + + /adUv04OXQkCFv+Oed6qktFVQ3E= + + hash2 + + lY5EZJwPc/Rmfhw1gotkeEKB+ANXqZUlM2G92sZwdJc= + + optional + + + Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings + + hash + + Mji9loJOJvuDY9hz3FhQ4H+HY5E= + + hash2 + + RUq6VJjn/QyydkNbpklLwfCgRF62+uHhXen2dYLBNuQ= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings + + hash + + 9U+OTz29kXKZHY/nmvbtemMsB3g= + + hash2 + + NNvDsecglQ/utR6YEqxyMj5K976YRWieCIC/PZuWCtQ= + + optional + + + Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings + + hash + + VpSLGNvZ6sbRYsF23L8m6TG+P6E= + + hash2 + + wJZ5NG+mvj4anRFPUFyvSD0kGrg+ZAqklsPfHuCxLQY= + + optional + + + Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings + + hash + + qn/mo2EFOyw6keezS64Wo5ZGZXU= + + hash2 + + e3cyzJ87ohC1ff/BzZ5O00MnwRE02U+J1KwXlSZeSSg= + + optional + + + Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings + + hash + + kwvdisufBenuQzrVg8tYKTX+qgg= + + hash2 + + t8QC+9TBONwKLQvV3fKV0umsnAS8ZDpqPikVksFPtWc= + + optional + + + Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings + + hash + + 98/sk+A2Ew1fmKpuKZ3rq8eS1EM= + + hash2 + + mJY6aeXFnSx38bF630z5lNPmPtsoYVAwadh0KC+9vfQ= + + optional + + + Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings + + hash + + HQwGW1Ebf0i+Bl4synks3x2SY2M= + + hash2 + + nlP7repbMz6EqHo3sZWnK3tzx47WKSWnULdUHCYPgKk= + + optional + + + Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings + + hash + + whUQco5F2wcYdjc+cPKlk+mtx7Q= + + hash2 + + xEXUfrylPld+eFGrPyj4wTRPj7vUWOZ2f94sWydq03M= + + optional + + + Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings + + hash + + JXhpqvLkX0yDWjbWgsk2wbSObKU= + + hash2 + + u0572QZYh6sB0GQdMGMePalOf4zkxE7YQG7pp898SEg= + + optional + + + Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings + + hash + + OnR96Z9tB0noODRSYssSs63+zGA= + + hash2 + + zvMbFdgVGI0ls9vIRT+sie7dj2g1UjQu7iS+pOgyBo4= + + optional + + + Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings + + hash + + 1FLKoM5jZ8JGBG/nmyEIA+/aalA= + + hash2 + + Vlf/4QD7/3S0SFqxmTWWcSwtTLWISKUSvLjpgWb7lxQ= + + optional + + + Resources/Autoupdate.app/Contents/_CodeSignature/CodeResources + + hash + + VHoMteoopEbbYVG87rxkm0sjkA4= + + hash2 + + lxIEfRslquAFN4m0UvN8ZbULT6U6J3cPAYY9sqR1HPE= + + + Resources/DarkAqua.css + + hash + + SCihC2/GG/DhF4xcXD9MYaxhawM= + + hash2 + + 1G4+GJId47E8UijYTy9xeqA8RpLdOaGZQZ8B85ydROg= + + + Resources/Info.plist + + hash + + Vjt5kOIAFdWzUgFgF/aP2v3owEc= + + hash2 + + F8ePDHN9JgUrW5JHoFYN8yT7d6AnZBL5ZflSVh3ri4Q= + + + Resources/SUModelTranslation.plist + + hash + + iD2Ex40Usc4ZE6IAhRePqgwK/xw= + + hash2 + + bxnpRQhROJXTL2xuIeffR3p+hOuuji7eOoDeNqSYqTg= + + + Resources/SUStatus.nib + + hash + + ECVWRExfxyDt5uvKRD+70wc9J6s= + + hash2 + + AtY9YmPv7cUlbFWP2vCyVdi3/M+XQn98wOlrIES2Dgk= + + + Resources/ar.lproj/SUAutomaticUpdateAlert.nib + + hash + + LtkONVbhTzwCPtbjkr06qSniXCI= + + hash2 + + CL6tBm495f4yM6z7y8UHRhtooR3NLGfDUOMHspa3d6k= + + optional + + + Resources/ar.lproj/SUUpdateAlert.nib + + hash + + JeZDdP1OuZbqkm8UKYiyH00A7ss= + + hash2 + + CE1qJ1jrwUiTKTlZajb/bhplzo/rdEH6pm5cABwD/rQ= + + optional + + + Resources/ar.lproj/SUUpdatePermissionPrompt.nib + + hash + + Heb65H1UseXl7rEaFwVxKauBWnI= + + hash2 + + WUx1KM1Bz75vbTlcj3FvUEEJ3niP2QNBe7/lPioCMgY= + + optional + + + Resources/ar.lproj/Sparkle.strings + + hash + + Rf4jjdgTqvfw5JO/6f9jHMURv/U= + + hash2 + + 2cAJJ5NTxwpRgp24Ca3EuTXfaIIzsYdH3Y9cNCalZfc= + + optional + + + Resources/ca.lproj/Sparkle.strings + + hash + + wGGx+QzPg/20zZTq7jwCTgf/Ubc= + + hash2 + + om5I6jKleuRoCwjfrRRqKWQbs2l8lLj8QGKS47cxybA= + + optional + + + Resources/cs.lproj/SUAutomaticUpdateAlert.nib + + hash + + YpT086oHMS9O2TvSNLZh+39oy80= + + hash2 + + YyE1WN1/ryPt2H0D9gYJv/r0SSv8VYTcxiiNeELiJIQ= + + optional + + + Resources/cs.lproj/SUUpdateAlert.nib + + hash + + v2ac1JQZvkm8EHZiTUc/q4aBcU0= + + hash2 + + LWRxgLZHNGYOe63gf0aOD8zoP03Z1s7ldxndzkWbzGw= + + optional + + + Resources/cs.lproj/SUUpdatePermissionPrompt.nib + + hash + + 2ANG1NY1o8ndm0xcmHwYUvrRk6w= + + hash2 + + dyM1bkEKAH1sW3J5pxDKHpNZ6ZJX7YH/x6jeICRqpkc= + + optional + + + Resources/cs.lproj/Sparkle.strings + + hash + + bY3rkqi/NJtXtjpK3FbV2o0gxbQ= + + hash2 + + RfJgT2b3STcLu71+1iU9ZcSXbfwMWG1EE1C7Wrf3xBk= + + optional + + + Resources/da.lproj/SUAutomaticUpdateAlert.nib + + hash + + XCqcLv38cTpbjAE4zjN/JWeT3+U= + + hash2 + + ivTLD912Rxy2BqIYFAQjsqh4PofwMLljqh6ncdYEdy8= + + optional + + + Resources/da.lproj/SUUpdateAlert.nib + + hash + + BY0imp6dA7C0GSOK81VXTJsRccM= + + hash2 + + BZL9bUc/f5RpZHoQGkA/XXKvykMh/LwkqI+1XW14Bxk= + + optional + + + Resources/da.lproj/SUUpdatePermissionPrompt.nib + + hash + + Ev2Nvw9c6bVU5ZF63yVhcyNp84w= + + hash2 + + N3Os+6xHdP9Y/QLv2okENWzAaaY73ZZ1wAa+vhQKSWg= + + optional + + + Resources/da.lproj/Sparkle.strings + + hash + + 0t7SuLDMBZVsY240PAEsVfH/1qw= + + hash2 + + wu0CpGqE79+TXKIQm+q7ycPTuXhOlwRr/wD5uGHJzLM= + + optional + + + Resources/de.lproj/SUAutomaticUpdateAlert.nib + + hash + + i/BaQXOSENNulhl0b5jssezuU3Y= + + hash2 + + vZn/tXvSzWZPBBo0cVnIiPjRYfiMEtABs2gGlmJ3DKo= + + optional + + + Resources/de.lproj/SUUpdateAlert.nib + + hash + + lieYpCoCaCKAA3EL3/EsBr46vqI= + + hash2 + + pOQG4CEenyMCs6E53Yf2+yYR99NwtjC9ESL6Hp719iM= + + optional + + + Resources/de.lproj/SUUpdatePermissionPrompt.nib + + hash + + oy0dHoyKmH2uV/KCHJzCagE+QIE= + + hash2 + + aXEwUfPGaGK1ndjF84VGCstTDsw+y3qn6bW9197R/wc= + + optional + + + Resources/de.lproj/Sparkle.strings + + hash + + fsC7FJvExHE/2681tuUrjkSF2+A= + + hash2 + + XUpgsFH8KmcbgggpdYbJScCg0tBic9tNLdFh+8cbPyw= + + optional + + + Resources/el.lproj/SUAutomaticUpdateAlert.nib + + hash + + s3rpfaKP5+1+vGc44qpcWy+h0t8= + + hash2 + + 8Dy4OJ7vlhUCXCV6cjyExPoQWOtUSRnuNLpDxfel5ss= + + optional + + + Resources/el.lproj/SUUpdateAlert.nib + + hash + + sJcnQqAH4BsB+2rz9riB7iqePh0= + + hash2 + + oZ9SfHTeIGNZtJjH75VsT01y5Vo2tq2VCPVF8bDddeE= + + optional + + + Resources/el.lproj/SUUpdatePermissionPrompt.nib + + hash + + mYyXqqWSoYqVG1zNp1vopIw8r1k= + + hash2 + + j3xNys0dFAL/2iqvjfz2PopHNj9kPZSLHI5SyE8Pb5c= + + optional + + + Resources/el.lproj/Sparkle.strings + + hash + + NbIN+TRHORCL5Gfj68VRq4KdPXo= + + hash2 + + wt+2xyusmWAQuJ5kAQlRlvFb1wO4L7/rFdG+VmNjl+Y= + + optional + + + Resources/en.lproj/SUAutomaticUpdateAlert.nib + + hash + + Y6bIF/+bAP3t5gBwPcdqxsj4co4= + + hash2 + + LK+XUVI/B5vkE00baFJQzgTVPcWQu2vfztwnjkmtAdg= + + optional + + + Resources/en.lproj/SUUpdateAlert.nib + + hash + + L3hoxekBQAtpmyDXNhTX7kRXRtc= + + hash2 + + c1eSzlRx9vqCBLiF84w+iiiGeii8RIOVaoC8Ds3gndI= + + optional + + + Resources/en.lproj/SUUpdatePermissionPrompt.nib + + hash + + uLKIwoprHw35+b4+/KP/j9X2zVg= + + hash2 + + kXFQNOUYJFVMleLIk/wvetRZoFi+Es/ChIGsKEkPdTs= + + optional + + + Resources/en.lproj/Sparkle.strings + + hash + + cHZov5FaqzfNhnBo0XdRuTMT4SY= + + hash2 + + 39CdfZZ1CQQz1Gd1+Ukxo2JHl0XESoc/cqWKF091WUk= + + optional + + + Resources/es.lproj/SUAutomaticUpdateAlert.nib + + hash + + BoS6NAq1zyVcmkbrKJhcI9Zrezk= + + hash2 + + GtD3UAnIT5BoshJo4areAKSruPfavkvTIyNd0gjejDM= + + optional + + + Resources/es.lproj/SUUpdateAlert.nib + + hash + + UMa1QcJf8zfpVUnIZUGFfJ64wTk= + + hash2 + + 3KPglR1oBAj4L7IA3Y4fYKtWrk2kpbl7jPZwPCByWfo= + + optional + + + Resources/es.lproj/SUUpdatePermissionPrompt.nib + + hash + + tre2iSm68OK3ztgNotyXuz1MkzI= + + hash2 + + nPkeNlTVifGs1wwWJbh4cKzN6KGsoSoFWhhX8gcD+Zc= + + optional + + + Resources/es.lproj/Sparkle.strings + + hash + + QPG88BN+x/l2Qk1NLLe3wRa26mQ= + + hash2 + + mtOoKdoTpGzeTNyzxkVGOMsE0Z3ZZOsmIKDfgA9aj8c= + + optional + + + Resources/fi.lproj/SUAutomaticUpdateAlert.nib + + hash + + 6aUnn3XSgWKnVuYVA/PVSrwora8= + + hash2 + + QVnq+Bn52L27jHSSWCd5PGIBzAeU4HwlTMuL0+M3JMI= + + optional + + + Resources/fi.lproj/SUUpdateAlert.nib + + hash + + k8QjxmBhk5B6v1fGnFkwwX6oakg= + + hash2 + + R5U4ry1iLGtnxFs4Ex8GdV7tpXKbo1HoH0rsSQKIO1M= + + optional + + + Resources/fi.lproj/SUUpdatePermissionPrompt.nib + + hash + + HB5ASms7UIZfv0WaGh6tCLBEDP8= + + hash2 + + QgdpF8+b+1E1FOKUQmo9gfa/0naxptxTvxgPAiBPnzc= + + optional + + + Resources/fi.lproj/Sparkle.strings + + hash + + yd6pIoSj19HMDIUos4Td1Fch7bs= + + hash2 + + +AiiKWEdH3lesozLJBn3tfK6vi/VSI1/TnWVmIdVVsc= + + optional + + + Resources/fr.lproj/SUAutomaticUpdateAlert.nib + + hash + + Nj5v0wIECbpjSTU74xKBngH4DeY= + + hash2 + + iJPr/YNl1hGgBNcbjpEttX7EgNKwsu+R3oVtXyoxxV0= + + optional + + + Resources/fr.lproj/SUUpdateAlert.nib + + hash + + RPgJubd38D/WH1H6B1jSejqILE8= + + hash2 + + KtHl8n7bnlZ3Ir0ymG0RdRWNezTCdzyBh9HO0AB2TrA= + + optional + + + Resources/fr.lproj/SUUpdatePermissionPrompt.nib + + hash + + cVJfS2Nx3QvdbWEq+tSt8xi9hIg= + + hash2 + + LTWELwsYH8j9IGZy23C/qmUvJ0/E498TrPfWsXFOM8c= + + optional + + + Resources/fr.lproj/Sparkle.strings + + hash + + X3URilwJPVqMTGbtrYdorODwrMA= + + hash2 + + fyqJl0MhXYRILalxRHpv/JorWLOVLPtNcJioiPtlnYg= + + optional + + + Resources/fr_CA.lproj + + symlink + fr.lproj + + Resources/he.lproj/Sparkle.strings + + hash + + U2WmlYGYmeeIlSW66R8awwmNXIE= + + hash2 + + 4gUlWkwTANV/jd7n4OZoXyT8CAcgWVk/tI3a25wmuLg= + + optional + + + Resources/hr.lproj/SUAutomaticUpdateAlert.nib + + hash + + SkgPcXJYp5dizLAgiXfyl9EsPoI= + + hash2 + + 2h4d04V7H95/KuIy1kBjzDQqtfxnZrRzKlxEjk3NYRo= + + optional + + + Resources/hr.lproj/SUUpdateAlert.nib + + hash + + 47iMWOA+94RZGJW+QJCeM4xOUsA= + + hash2 + + ReDJwTVXlm8iJWToPp6haL3A35LkgyNtEYfEYQ38l+A= + + optional + + + Resources/hr.lproj/SUUpdatePermissionPrompt.nib + + hash + + S2YV0JmEwfPtYsMBBMuvddrPEis= + + hash2 + + s4Q/66AafiJ25LzYFLwYJRMdqr1W2awMSkxlZjy9JtM= + + optional + + + Resources/hr.lproj/Sparkle.strings + + hash + + 7LLOVs76ioMwEDV8Gah+6sV/5No= + + hash2 + + TwklhrooHTXgV6Q9fbvvAB3mPIh7qDbEsNtUzo2fQuU= + + optional + + + Resources/hu.lproj/SUAutomaticUpdateAlert.nib + + hash + + PfYCBbOThC1gBDzoxD+ijdyQ3T0= + + hash2 + + C9vH4mEYy0VzQEvjXYfCMPM4ggBQF1APABRkUOUQwPA= + + optional + + + Resources/hu.lproj/SUUpdateAlert.nib + + hash + + 6RlHCvHc9GNh1M7iJhvn12iFGpg= + + hash2 + + 8vAkRUe47lFmMm7zUZM55/XRK21KahmSbRy0Axp6gw0= + + optional + + + Resources/hu.lproj/SUUpdatePermissionPrompt.nib + + hash + + 1Yz7vPBCFCly2cHjtbQJPK9PzjE= + + hash2 + + GyEkgG0mW1s+T6Nz7aQ/eEvLYoysvr7BYots62oHX1w= + + optional + + + Resources/hu.lproj/Sparkle.strings + + hash + + bNEmsO2LyUsMjTESH1I42V9sAOo= + + hash2 + + sRkp8c3Bx1qWdhhSNdOap1PbfmiTziINy1HxGea3SWU= + + optional + + + Resources/is.lproj/SUAutomaticUpdateAlert.nib + + hash + + 9eOJ/dQvTMu45Z1UowMPaKHYQOI= + + hash2 + + QpgLimdJiwdp2DVF/DMQyJ2Zg8L2ihsreE7vcI8Uqh0= + + optional + + + Resources/is.lproj/SUUpdateAlert.nib + + hash + + F0aP96zh7QOSkAdFsBlIzBhmCIg= + + hash2 + + t7A1i/lrse5T6UZtyfTdB/7HRH5vPNuOj2I+QkIjAEI= + + optional + + + Resources/is.lproj/SUUpdatePermissionPrompt.nib + + hash + + xRBgLwOX0xZhrXGjHDHL6S+qCQc= + + hash2 + + VWBW48lOFIc7lprCjCV9s4BfRYheTgsJnhe5dnQbqOY= + + optional + + + Resources/is.lproj/Sparkle.strings + + hash + + 8fxzD9ZhrvIZVZB1+QSJaPzg80M= + + hash2 + + xcV1yh/zU3U3TsRUT6vGybvIQitf+ThrogN/uOWmD8k= + + optional + + + Resources/it.lproj/SUAutomaticUpdateAlert.nib + + hash + + JXGMS3rtry8HTWEbBibxVCkBEmw= + + hash2 + + 5+DJlDvCzlPxtarex6vBp6GBNUjc//XUg1dP+YKgQvo= + + optional + + + Resources/it.lproj/SUUpdateAlert.nib + + hash + + W45+n9zTkxt1E3v6cZYLzXNtDlc= + + hash2 + + dMHKLXO9jQ/ephXEzJ8zaCiJ2TD94Xdtlfwqn0liUVo= + + optional + + + Resources/it.lproj/SUUpdatePermissionPrompt.nib + + hash + + o6d6uYDAajCHTJJOXT7zDECTmIM= + + hash2 + + abK36qjugFrOyJCWuehmLWtrUUaY2xV+kVIPmNJXKjA= + + optional + + + Resources/it.lproj/Sparkle.strings + + hash + + bk1J6vpZjWeUFhBYWuWZf8TDv1A= + + hash2 + + Y+caNW+g0mt7HP4JrBxJw+uDwN3j19UYb+q5r9ch4Ow= + + optional + + + Resources/ja.lproj/SUAutomaticUpdateAlert.nib + + hash + + 9fRyIZvZMUaRTaWCda7NsPqJle0= + + hash2 + + 17Hm2P7maddnurefS4zzxjw/i66hgAqMfPCnw5etp8c= + + optional + + + Resources/ja.lproj/SUUpdateAlert.nib + + hash + + L07PCZt4pHgRzMPxV0N6F2QK9kM= + + hash2 + + qzSS0s4cMsrK7155WvW8tp+ToVcFs5pmCwrBC9Lr/ec= + + optional + + + Resources/ja.lproj/SUUpdatePermissionPrompt.nib + + hash + + b1mgRruuDPTLXfhBEjTV72kV1m0= + + hash2 + + 5nEPrrpTyzn6ealGjKbkHDtrZ2hvu6zXmQjlL7x8UGY= + + optional + + + Resources/ja.lproj/Sparkle.strings + + hash + + f4EbR/GfMsKeWJ5DN/vhwg/lUoE= + + hash2 + + dSPIvpFbelHRv8liJjN3TUVPbgD1DfhVSGmE+S99quI= + + optional + + + Resources/ko.lproj/SUAutomaticUpdateAlert.nib + + hash + + gi+8llNjjuFSKRkNAA7vN/mf1uA= + + hash2 + + xhSLkCd/oWSClxzjFJOGmPOAS3u0od2to6thPF+1hi8= + + optional + + + Resources/ko.lproj/SUUpdateAlert.nib + + hash + + UNAQQTvtG7+MN/4w4ouu9ZHXfCM= + + hash2 + + rU7gnpi3PsnD0n0noPJN7LKUwxXFHCjixcW7WHa1q2Y= + + optional + + + Resources/ko.lproj/SUUpdatePermissionPrompt.nib + + hash + + ycrHyxLA68Lf8rq4IXBVK62UpGc= + + hash2 + + bvNquF3puRjnZvG2nQUQsz5WyXUsO3LC6BCtEjw2+9g= + + optional + + + Resources/ko.lproj/Sparkle.strings + + hash + + FRHRQPCWEk9GdJawYTuccg+E2tA= + + hash2 + + +bxn0NPgkxdHLa1MHRT+JRlYmy1jpIuaenpst5RT+RA= + + optional + + + Resources/nb.lproj/SUAutomaticUpdateAlert.nib + + hash + + kJLypTD4VsCOwsXiOd6700pn0Cc= + + hash2 + + k7N16zCtXUz9SCO2rA2PSAb+B9zsqeF9kPAlIPeRQq0= + + optional + + + Resources/nb.lproj/SUUpdateAlert.nib + + hash + + WIJIO1qR0uNQtJpVkhIarVOfgFw= + + hash2 + + 8g/ElSO4yIXKBPWT/sbST8vdzCsbEVIeHZSxJbCzJ4M= + + optional + + + Resources/nb.lproj/SUUpdatePermissionPrompt.nib + + hash + + sGjRl91qI6175CwJYuqSYLYylJg= + + hash2 + + /oxFNXMvdoD2D7ykStspwXUrcEpRCaVbYebxNXyRuI4= + + optional + + + Resources/nb.lproj/Sparkle.strings + + hash + + sgrDElwUxXtzdw8WaUFWyK3pG9Y= + + hash2 + + FG+w+OnLI7nwnNCWiMT50LU98VWj1d08ElfX4k7Ok4w= + + optional + + + Resources/nl.lproj/SUAutomaticUpdateAlert.nib + + hash + + wPmqlbK7p5mjPDrezXrYK8CG3NA= + + hash2 + + zV9zmt6+b1fbS8Nzwh35PAkW5vdYcJ64kjUjQyde+Mo= + + optional + + + Resources/nl.lproj/SUUpdateAlert.nib + + hash + + 7EXAJEeeN0k32jvOHKr7Icq4644= + + hash2 + + 3I8r+QxaqIEqBcdZogXU5VuDg87Ls1S5Ss1nhmfM8Po= + + optional + + + Resources/nl.lproj/SUUpdatePermissionPrompt.nib + + hash + + QHLHLZGOJJ4eN75aG1K1VUHNPz8= + + hash2 + + arRlqOWae64Pqn6yNw466S0RNtPGSxvbi7FEd00g1x8= + + optional + + + Resources/nl.lproj/Sparkle.strings + + hash + + PWbC08zHFLROqivY2MAklDh6gkA= + + hash2 + + xnQkqxaO8zP1xpjY3nyjOd4Fe0gJon2Dbt456ukd/Gw= + + optional + + + Resources/pl.lproj/SUAutomaticUpdateAlert.nib + + hash + + 4aIS8LbPLTj63EhS7xEk+qjNzD8= + + hash2 + + PvUb+gVq9mfBJ4c4Rp3cyMOr6Fw7EqVwOSYCXdskwUA= + + optional + + + Resources/pl.lproj/SUUpdateAlert.nib + + hash + + pLII26utl65JwmAFqLiMefDsrGs= + + hash2 + + KNo3HyQuwczwWYBLgpAwz1nk4Lo/IuCN4SZpFhn8diY= + + optional + + + Resources/pl.lproj/SUUpdatePermissionPrompt.nib + + hash + + pZo0pXve6jqBertG5IixAzSpgV4= + + hash2 + + ILzDGh3jJc3hPKR/ADk0jiaDQaHZu7Zq8YLRXT+OXjc= + + optional + + + Resources/pl.lproj/Sparkle.strings + + hash + + o7deBXE2Ct8/vQxouej5KkwTcUA= + + hash2 + + pDq+41jhfESgJauedrYncFY1O5EMEU3nRyl7mmyYj+s= + + optional + + + Resources/pt.lproj + + symlink + pt_BR.lproj + + Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib + + hash + + 8fQxJZw+LQiExfyPaqUbNnASsWU= + + hash2 + + 7Xu6H76LF/BEqK9o0LJHpt54NV3JbY/TPoinZv9GfSQ= + + optional + + + Resources/pt_BR.lproj/SUUpdateAlert.nib + + hash + + zW5zEa6rDpqLuTDfixuKKE93E5o= + + hash2 + + fbgvSsPkysitkmLfP26OmYpP/r+044gpfMHqQoQp9Ns= + + optional + + + Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib + + hash + + 2dtbduILRWtmfjobyd2yOVhQNH4= + + hash2 + + 18VMio0CRtcvr8i0M+O3/t41QS15KVxSGxoVhQYapxE= + + optional + + + Resources/pt_BR.lproj/Sparkle.strings + + hash + + /adUv04OXQkCFv+Oed6qktFVQ3E= + + hash2 + + lY5EZJwPc/Rmfhw1gotkeEKB+ANXqZUlM2G92sZwdJc= + + optional + + + Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib + + hash + + Svc6e9pHrp1wA7rxw7oKc5HB8qQ= + + hash2 + + L4KDh1UJEm3ta+qgzCe3s3RI7xXrAY8y5h5eqneS3Uo= + + optional + + + Resources/pt_PT.lproj/SUUpdateAlert.nib + + hash + + NtD144OppS+BPXNmsReGJoN2Qdo= + + hash2 + + NOQ75dz/Mq7PLhwssExcWXdtTFQzx8m/lLpLBDCaZCI= + + optional + + + Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib + + hash + + 5B8hYUrKag0Unyt6Uk0D2K5opL8= + + hash2 + + D42TQ5lV8E73WOOXTsUDSu2jqOFUt1+WMcLUTVab8W8= + + optional + + + Resources/pt_PT.lproj/Sparkle.strings + + hash + + Mji9loJOJvuDY9hz3FhQ4H+HY5E= + + hash2 + + RUq6VJjn/QyydkNbpklLwfCgRF62+uHhXen2dYLBNuQ= + + optional + + + Resources/ro.lproj/SUAutomaticUpdateAlert.nib + + hash + + 7HEo1dlbwSnit0+4DsAqKDz1jR4= + + hash2 + + KUqcmkrRCh+XjXh9F7fudt94MreG4bKqDh1PLat/FpI= + + optional + + + Resources/ro.lproj/SUUpdateAlert.nib + + hash + + FPWtaRuYrVSPrfAozq/4bSQfMK4= + + hash2 + + 2mIpfTwoRvVuhY/Aa3Bqw5VsjpV93xFNcBMQG83Q8DU= + + optional + + + Resources/ro.lproj/SUUpdatePermissionPrompt.nib + + hash + + GY/ufItfyKYpgw54TfqJlPlymb0= + + hash2 + + hMvl0YhsMlkusdQxVcUiUDeQQqNn/KFwXfIqWaDSrG0= + + optional + + + Resources/ro.lproj/Sparkle.strings + + hash + + 9U+OTz29kXKZHY/nmvbtemMsB3g= + + hash2 + + NNvDsecglQ/utR6YEqxyMj5K976YRWieCIC/PZuWCtQ= + + optional + + + Resources/ru.lproj/SUAutomaticUpdateAlert.nib + + hash + + YHane6xWVhvpJGf5HuoxCyQ/gDs= + + hash2 + + qwuyYXCO4H5IuXiRhZR3ucGl5S1CtG2e7kxxCkM8vtA= + + optional + + + Resources/ru.lproj/SUUpdateAlert.nib + + hash + + NxM+W+qAegxK4lKy0uzCclpkVjo= + + hash2 + + Kvykt9h+o+SUuINROlMCXornVL/uEH2Uz5Kd0bNSm6k= + + optional + + + Resources/ru.lproj/SUUpdatePermissionPrompt.nib + + hash + + AqJRrBMp2yA+umSXxQIQVmpnCN4= + + hash2 + + 7Guwtd2PDpjTWg/qX1UODxgRfQDDxNUgN4qr4Siv/Cc= + + optional + + + Resources/ru.lproj/Sparkle.strings + + hash + + VpSLGNvZ6sbRYsF23L8m6TG+P6E= + + hash2 + + wJZ5NG+mvj4anRFPUFyvSD0kGrg+ZAqklsPfHuCxLQY= + + optional + + + Resources/sk.lproj/SUAutomaticUpdateAlert.nib + + hash + + 5k+e1kFtgoVo77RhyhZSXpRQGOQ= + + hash2 + + qZSxDMjtIXyNwOZYeUz2g9w6NFmKFStqy5UbKU9N3BA= + + optional + + + Resources/sk.lproj/SUUpdateAlert.nib + + hash + + l8XR02tvN10SX2aM9CtZ7BpIcqw= + + hash2 + + fD0W6cMr/MZ0C0qrsMD3jEbXdK+eq8L7tl87ZPDspmY= + + optional + + + Resources/sk.lproj/SUUpdatePermissionPrompt.nib + + hash + + burLhfFkzbPjAIqMXw1qKn94xm8= + + hash2 + + D5XPrSBItdufc2zVCFazX3SEmRVqQBdCZ0ADlLmHE4E= + + optional + + + Resources/sk.lproj/Sparkle.strings + + hash + + qn/mo2EFOyw6keezS64Wo5ZGZXU= + + hash2 + + e3cyzJ87ohC1ff/BzZ5O00MnwRE02U+J1KwXlSZeSSg= + + optional + + + Resources/sl.lproj/SUAutomaticUpdateAlert.nib + + hash + + htsZnsf4S+omnk4Z8En8qOOjrPM= + + hash2 + + 9kzvdHcdVqdxsioITBt+Am06twXcJpKMreMKHDDj4RY= + + optional + + + Resources/sl.lproj/SUUpdateAlert.nib + + hash + + /9xrGwHXOdPKFp82w4OjF+Q0WG8= + + hash2 + + r48ahwyC8EFi+44X/EtUfWvh8QCu9klpjqwwwzQHiXE= + + optional + + + Resources/sl.lproj/SUUpdatePermissionPrompt.nib + + hash + + lT25Wn73ZrIgcDJsgzSvIQ97TtY= + + hash2 + + L8TvfX3/5nLCXk5oNeUzyieunqgEUrKzk/t+6Ldxn/0= + + optional + + + Resources/sl.lproj/Sparkle.strings + + hash + + kwvdisufBenuQzrVg8tYKTX+qgg= + + hash2 + + t8QC+9TBONwKLQvV3fKV0umsnAS8ZDpqPikVksFPtWc= + + optional + + + Resources/sv.lproj/SUAutomaticUpdateAlert.nib + + hash + + K0QpkucJxh5CRA9TYJCT5+gSHZY= + + hash2 + + 2GEt92QWRuf/s8NnoWD/HidalRNoLUoeJoC7UM3Y2Ng= + + optional + + + Resources/sv.lproj/SUUpdateAlert.nib + + hash + + dOxfomMC/X9MFFdsk1MyjKv1yi8= + + hash2 + + wIPOqj0JUePKauMIl6sT8YUjoxw1q36g+rrQvwkxfw4= + + optional + + + Resources/sv.lproj/SUUpdatePermissionPrompt.nib + + hash + + W5vyz7ueX3DVKxQC82/3FnvJfeQ= + + hash2 + + C0r3bNAO7WAAZa4WiYUeBQMt+kmEndHyzPT1//HBsYo= + + optional + + + Resources/sv.lproj/Sparkle.strings + + hash + + 98/sk+A2Ew1fmKpuKZ3rq8eS1EM= + + hash2 + + mJY6aeXFnSx38bF630z5lNPmPtsoYVAwadh0KC+9vfQ= + + optional + + + Resources/th.lproj/SUAutomaticUpdateAlert.nib + + hash + + /iyQcDW4+Is40OSlKcWlEe9zKdo= + + hash2 + + gFeu5nmtM/aL1yTSsW9TPh9ZdN8r/lX5jhns53LlcfA= + + optional + + + Resources/th.lproj/SUUpdateAlert.nib + + hash + + cQCWeOMdd6mZEd9k0pl3FrZDT9g= + + hash2 + + vmbxfC0cI7IAAQRNDQ43V6vocH93TiejQmBr23NDAlQ= + + optional + + + Resources/th.lproj/SUUpdatePermissionPrompt.nib + + hash + + JIh/Ueyxh8+us+26dxQRmrPiVAE= + + hash2 + + Hbc+V8H4hfQh3PucUY1S+6vXRBZErhTaZLYJ0jQooLo= + + optional + + + Resources/th.lproj/Sparkle.strings + + hash + + HQwGW1Ebf0i+Bl4synks3x2SY2M= + + hash2 + + nlP7repbMz6EqHo3sZWnK3tzx47WKSWnULdUHCYPgKk= + + optional + + + Resources/tr.lproj/SUAutomaticUpdateAlert.nib + + hash + + fjnMF3XlYLFPJSpmIFizoGwOVl8= + + hash2 + + 3T/cO/cEIAM8QBGVg53bYvcYseEMPOhn0C+yReVVJEU= + + optional + + + Resources/tr.lproj/SUUpdateAlert.nib + + hash + + Y9dabfD0a7F1cV9OuFnyQL5BIIc= + + hash2 + + qwss8xg3cxWWQXqCLxXd6z1ygf6MHYjMDKtVGzTpF0M= + + optional + + + Resources/tr.lproj/SUUpdatePermissionPrompt.nib + + hash + + 5wxy4Op51XjVl1MvUlCnSUfvsj8= + + hash2 + + eXL2eqxEpbACqNQkixNqqVrLeqRbdnpPCndQcIXqdas= + + optional + + + Resources/tr.lproj/Sparkle.strings + + hash + + whUQco5F2wcYdjc+cPKlk+mtx7Q= + + hash2 + + xEXUfrylPld+eFGrPyj4wTRPj7vUWOZ2f94sWydq03M= + + optional + + + Resources/uk.lproj/SUAutomaticUpdateAlert.nib + + hash + + YRRVgJ26NZd9+ebTI3UGdpi35eo= + + hash2 + + /lUZP3n163WFPUjg+ohhY7pnbmtrxuc164ew0tFmDd4= + + optional + + + Resources/uk.lproj/SUUpdateAlert.nib + + hash + + PoeaXUHUKNIm0bkX+GNnvFHlq9w= + + hash2 + + Pmyouw5QFzbN7VYg1RXFNm3IB4jOmZagTi9k8g2CyQE= + + optional + + + Resources/uk.lproj/SUUpdatePermissionPrompt.nib + + hash + + HEubU7VtIHZcWJ6RfdC038Os1gw= + + hash2 + + 7ljWihx3qnfD/0BWdbNtzQirBF95hZ1sSXu5vTiVHe0= + + optional + + + Resources/uk.lproj/Sparkle.strings + + hash + + JXhpqvLkX0yDWjbWgsk2wbSObKU= + + hash2 + + u0572QZYh6sB0GQdMGMePalOf4zkxE7YQG7pp898SEg= + + optional + + + Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib + + hash + + 2pvLfCu7EiI6OkCxu3+aLyeTPcU= + + hash2 + + tu9oMdBCiHiyRSJMEmm5x3oGY3aCnmwXPsRf1hjj7fY= + + optional + + + Resources/zh_CN.lproj/SUUpdateAlert.nib + + hash + + +xvQE3bFW1QXIUggZBlZkKn0gag= + + hash2 + + b6nNjM4vCBrXBbjH5GtbkPjZjJyMeSSBXO/tCRn+LFY= + + optional + + + Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib + + hash + + FT+kQgUNxKGrbheU8uSqkYFSHtI= + + hash2 + + 2J873kYqy/0EyE6QXT6tqYkydTGcrw3/Ncv9cnSRl7M= + + optional + + + Resources/zh_CN.lproj/Sparkle.strings + + hash + + OnR96Z9tB0noODRSYssSs63+zGA= + + hash2 + + zvMbFdgVGI0ls9vIRT+sie7dj2g1UjQu7iS+pOgyBo4= + + optional + + + Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib + + hash + + KQcqbpIyw3yhI0eEyo06cNq5MlM= + + hash2 + + POFOdG9uGjcVXkx39sW2Rwl2FJ2zs6A007yF1UpDskE= + + optional + + + Resources/zh_TW.lproj/SUUpdateAlert.nib + + hash + + 6/Rgln3/89vly1RFa1gBfRhITxU= + + hash2 + + AFd6Yy2xbmYtz1+r9u+r9qNka3oTzho3/n3DCwxWKoE= + + optional + + + Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib + + hash + + qO2OAmNcqk2/bSzwAjGcXTD4+PY= + + hash2 + + JNnNodE8g22fkHlnQzxC9vap/jefD5NkjUjmErvl940= + + optional + + + Resources/zh_TW.lproj/Sparkle.strings + + hash + + 1FLKoM5jZ8JGBG/nmyEIA+/aalA= + + hash2 + + Vlf/4QD7/3S0SFqxmTWWcSwtTLWISKUSvLjpgWb7lxQ= + + optional + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Sparkle.framework/Versions/Current b/Sparkle.framework/Versions/Current new file mode 120000 index 00000000..8c7e5a66 --- /dev/null +++ b/Sparkle.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/TimerWindowController.h b/TimerWindowController.h index 01263975..36ec5a11 100755 --- a/TimerWindowController.h +++ b/TimerWindowController.h @@ -22,7 +22,6 @@ #import #import "AppController.h" -#import "SelfControlCommon.h" // A subclass of NSWindowController created to manage the floating timer window // which tells the user how much time remains in the block. diff --git a/TimerWindowController.m b/TimerWindowController.m index 3f8de345..c5f9f5e0 100755 --- a/TimerWindowController.m +++ b/TimerWindowController.m @@ -23,7 +23,6 @@ #import "TimerWindowController.h" -#import "SCUtilities.h" @interface TimerWindowController () @@ -35,7 +34,7 @@ @implementation TimerWindowController - (TimerWindowController*) init { if(self = [super init]) { - settings_ = [SCSettings currentUserSettings]; + settings_ = [SCSettings sharedSettings]; // We need a block to prevent us from running multiple copies of the "Add to Block" // sheet. @@ -76,7 +75,20 @@ - (void)awakeFromNib { addToBlockButton_.hidden = NO; extendBlockButton_.hidden = NO; - blockEndingDate_ = [settings_ valueForKey: @"BlockEndDate"]; + if ([SCBlockUtilities modernBlockIsRunning]) { + blockEndingDate_ = [settings_ valueForKey: @"BlockEndDate"]; + } else { + // legacy block! + blockEndingDate_ = [SCMigrationUtilities legacyBlockEndDate]; + + // if it's a legacy block, we will disable some features + // since it's too difficult to get these working across versions. + // the user will just have to wait until their next block to do these things! + if ([SCBlockUtilities legacyBlockIsRunning]) { + addToBlockButton_.enabled = YES; + extendBlockButton_.enabled = YES; + } + } [self updateTimerDisplay: nil]; @@ -89,25 +101,30 @@ - (void)awakeFromNib { //If the dialog isn't focused, instead of getting a NSTimer, we get null. //Scheduling the timer from the main thread seems to work. [self performSelectorOnMainThread: @selector(hackAroundMainThreadtimer:) withObject: timerUpdater_ waitUntilDone: YES]; + + // the timer is a good time to prompt them to enable error reporting! nothing else is happening + [NSTimer scheduledTimerWithTimeInterval: 3.0 repeats: NO block:^(NSTimer * _Nonnull timer) { + [SCSentry showErrorReportingPromptIfNeeded]; + }]; } - (void)blockEnded { - if(![SCUtilities blockIsRunningWithSettings: settings_ defaults: [NSUserDefaults standardUserDefaults]]) { - [timerUpdater_ invalidate]; - timerUpdater_ = nil; + [timerUpdater_ invalidate]; + timerUpdater_ = nil; - [timerLabel_ setStringValue: NSLocalizedString(@"Block not active", @"block not active string")]; - [timerLabel_ setFont: [[NSFontManager sharedFontManager] - convertFont: [timerLabel_ font] - toSize: 37] - ]; + [timerLabel_ setStringValue: NSLocalizedString(@"Block not active", @"block not active string")]; + [timerLabel_ setFont: [[NSFontManager sharedFontManager] + convertFont: [timerLabel_ font] + toSize: 37] + ]; - [timerLabel_ sizeToFit]; + [timerLabel_ sizeToFit]; - [self resetStrikes]; - } + [self resetStrikes]; + + [SCSentry addBreadcrumb: @"Block ended and timer window is closing" category: @"app"]; } @@ -138,7 +155,12 @@ - (void)updateTimerDisplay:(NSTimer*)timer { if(numStrikes > 10) { // OK, this is taking longer than it should. Enable manual block removal. - if (numStrikes == 10) NSLog(@"WARNING: Block should have ended a minute ago! Probable failure to remove."); + if (numStrikes == 10) { + NSLog(@"WARNING: Block should have ended a minute ago! Probable failure to remove."); + NSError* err = [SCErr errorWithCode: 105]; + [SCSentry captureError: err]; + } + addToBlockButton_.hidden = YES; extendBlockButton_.hidden = YES; killBlockButton_.hidden = NO; @@ -191,7 +213,10 @@ - (void)updateTimerDisplay:(NSTimer*)timer { } // make sure add to list is disabled if it's an allowlist block - addToBlockButton_.hidden = [[settings_ valueForKey: @"BlockAsWhitelist"] boolValue]; + // don't worry about it for a legacy block! the buttons are disabled anyway so it doesn't matter + if ([SCBlockUtilities modernBlockIsRunning]) { + addToBlockButton_.hidden = [settings_ boolForKey: @"ActiveBlockAsWhitelist"]; + } } - (void)windowShouldClose:(NSNotification *)notification { @@ -209,11 +234,9 @@ - (IBAction) addToBlock:(id)sender { return; } - [NSApp beginSheet: addSheet_ - modalForWindow: [self window] - modalDelegate: self - didEndSelector: @selector(didEndSheet:returnCode:contextInfo:) - contextInfo: nil]; + [self.window beginSheet: addSheet_ completionHandler:^(NSModalResponse returnCode) { + [self->addSheet_ orderOut: self]; + }]; [modifyBlockLock unlock]; } @@ -225,11 +248,9 @@ - (IBAction) extendBlockTime:(id)sender { return; } - [NSApp beginSheet: extendBlockTimeSheet_ - modalForWindow: [self window] - modalDelegate: self - didEndSelector: @selector(didEndSheet:returnCode:contextInfo:) - contextInfo: nil]; + [self.window beginSheet: extendBlockTimeSheet_ completionHandler:^(NSModalResponse returnCode) { + [self->extendBlockTimeSheet_ orderOut: self]; + }]; [modifyBlockLock unlock]; } @@ -255,7 +276,13 @@ - (IBAction) performExtendBlock:(id)sender { } - (void) blockEndDateUpdated { - blockEndingDate_ = [settings_ valueForKey: @"BlockEndDate"]; + if ([SCBlockUtilities modernBlockIsRunning]) { + blockEndingDate_ = [settings_ valueForKey: @"BlockEndDate"]; + + } else { + // legacy block! + blockEndingDate_ = [SCMigrationUtilities legacyBlockEndDate]; + } [self performSelectorOnMainThread: @selector(updateTimerDisplay:) withObject:nil waitUntilDone: YES]; } @@ -294,8 +321,11 @@ - (IBAction)killBlock:(id)sender { &authorizationRef); if(status) { - NSLog(@"ERROR: Failed to authorize block kill."); - return; + if (status != AUTH_CANCELLED_STATUS) { + NSError* err = [SCErr errorWithCode: 501]; + [SCSentry captureError: err]; + } + return; } // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid @@ -317,8 +347,8 @@ - (IBAction)killBlock:(id)sender { if(status) { NSLog(@"WARNING: Authorized execution of helper tool returned failure status code %d", status); - NSError* err = [NSError errorWithDomain: @"org.eyebeam.SelfControl-Killer" code: status userInfo: @{NSLocalizedDescriptionKey: @"Error executing privileged helper tool."}]; - + NSError* err = [SCErr errorWithCode: 400]; + [SCSentry captureError: err]; [NSApp presentError: err]; return; @@ -331,11 +361,8 @@ - (IBAction)killBlock:(id)sender { if (bytesRead < 1) break; } - // Now that[ the current block is over, we can go ahead and remove the legacy block info - // and migrate them to the new SCSettings system - // (and reload settings so the timer window knows the block is done) - [[SCSettings currentUserSettings] reloadSettings]; - [[SCSettings currentUserSettings] clearLegacySettings]; + // reload settings so the timer window knows the block is done + [[SCSettings sharedSettings] reloadSettings]; // update the UI _before_ we run the alert, // so the main window doesn't steal the focus from the alert @@ -344,6 +371,13 @@ - (IBAction)killBlock:(id)sender { withObject:nil waitUntilDone:YES]; + // send some debug info to Sentry to help us track this issue + [SCSentry captureMessage: @"User manually cleared SelfControl block from the timer window"]; +// [SCSentry captureMessage: @"User manually cleared SelfControl block from the timer window" withScopeBlock:^(SentryScope * _Nonnull scope) { +// SentryAttachment* fileAttachment = [[SentryAttachment alloc] initWithPath: [@"~/Documents/SelfControl-Killer.log" stringByExpandingTildeInPath]]; +// [scope addAttachment: fileAttachment]; +// }]; + NSAlert* alert = [[NSAlert alloc] init]; [alert setMessageText: @"Success!"]; [alert setInformativeText:@"The block was cleared successfully. You can find the log file, named SelfControl-Killer.log, in your Documents folder. If you're still having issues, please check out the SelfControl FAQ on GitHub."]; diff --git a/cli-main.m b/cli-main.m new file mode 100755 index 00000000..42592279 --- /dev/null +++ b/cli-main.m @@ -0,0 +1,198 @@ +// +// cli-main.m +// SelfControl +// +// Created by Charlie Stigler on 2/4/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "PacketFilter.h" +#import "SCHelperToolUtilities.h" +#import "SCSettings.h" +#import "SCXPCClient.h" +#import "SCBlockFileReaderWriter.h" +#import + +// The main method which deals which most of the logic flow and execution of +// the CLI tool. +int main(int argc, char* argv[]) { + [SCSentry startSentry: @"org.eyebeam.selfcontrol-cli"]; + + @autoreleasepool { + if(argc < 3 || argv[1] == NULL || argv[2] == NULL) { + NSLog(@"ERROR: Not enough arguments"); + exit(EX_USAGE); + } + + NSString* modeString = @(argv[2]); + // We'll need the controlling UID to know what settings to read + uid_t controllingUID = [@(argv[1]) intValue]; + + SCSettings* settings = [SCSettings sharedSettings]; + + NSDictionary* defaultsDict; + // if we're running as root/sudo and we have a controlling UID, use defaults for the controlling user (legacy behavior) + // otherwise, just use the current user's defaults (modern behavior) + if (geteuid() == 0 && controllingUID > 0) { + defaultsDict = [SCMiscUtilities defaultsDictForUser: controllingUID]; + } else { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults registerDefaults: SCConstants.defaultUserDefaults]; + defaultsDict = defaults.dictionaryRepresentation; + } + + if([modeString isEqual: @"--install"]) { + [SCSentry addBreadcrumb: @"CLI method --install called" category: @"cli"]; + + if ([SCBlockUtilities anyBlockIsRunning]) { + NSLog(@"ERROR: Block is already running"); + exit(EX_CONFIG); + } + + NSArray* blocklist; + NSDate* blockEndDate; + BOOL blockAsWhitelist = NO; + NSDictionary* blockSettings; + + // there are two ways we can read in the core block parameters (Blocklist, BlockEndDate, BlockAsWhitelist): + // 1) we can receive them as command-line arguments, including a path to a blocklist file + // 2) we can read them from user defaults (for legacy support, don't encouarge this) + NSString* pathToBlocklistFile; + NSDate* blockEndDateArg; + if (argv[3] != NULL && argv[4] != NULL) { + pathToBlocklistFile = @(argv[3]); + blockEndDateArg = [[NSISO8601DateFormatter new] dateFromString: @(argv[4])]; + + // if we didn't get a valid block end date in the future, ignore the other args + if (blockEndDateArg == nil || [blockEndDateArg timeIntervalSinceNow] < 1) { + pathToBlocklistFile = nil; + NSLog(@"Error: Block end date argument %@ is invalid", @(argv[4])); + exit(EX_IOERR); + } else { + blockEndDate = blockEndDateArg; + NSDictionary* readProperties = [SCBlockFileReaderWriter readBlocklistFromFile: [NSURL fileURLWithPath: pathToBlocklistFile]]; + + if (readProperties == nil) { + NSLog(@"ERROR: Block could not be read from file %@", pathToBlocklistFile); + exit(EX_IOERR); + } + + blocklist = readProperties[@"Blocklist"]; + blockAsWhitelist = [readProperties[@"BlockAsWhitelist"] boolValue]; + } + } else { + blocklist = defaultsDict[@"Blocklist"]; + blockAsWhitelist = [defaultsDict[@"BlockAsWhitelist"] boolValue]; + + NSTimeInterval blockDurationSecs = MAX([defaultsDict[@"BlockDuration"] intValue] * 60, 0); + blockEndDate = [NSDate dateWithTimeIntervalSinceNow: blockDurationSecs]; + } + + // read in the other block settings, for now only accepted via defaults + // TODO: accept these via arguments also + blockSettings = @{ + @"ClearCaches": defaultsDict[@"ClearCaches"], + @"AllowLocalNetworks": defaultsDict[@"AllowLocalNetworks"], + @"EvaluateCommonSubdomains": defaultsDict[@"EvaluateCommonSubdomains"], + @"IncludeLinkedDomains": defaultsDict[@"IncludeLinkedDomains"], + @"BlockSoundShouldPlay": defaultsDict[@"BlockSoundShouldPlay"], + @"BlockSound": defaultsDict[@"BlockSound"], + @"EnableErrorReporting": defaultsDict[@"EnableErrorReporting"] + }; + + if([blocklist count] == 0 || [blockEndDate timeIntervalSinceNow] < 1) { + // ya can't start a block without a blocklist, and it can't run for less than a second + // because that's silly + NSLog(@"ERROR: Blocklist is empty, or block does not end in the future."); + exit(EX_CONFIG); + } + + // We should try to delete the old helper tool if it exists, to avoid confusion + NSFileManager* fileManager = [NSFileManager defaultManager]; + if([fileManager fileExistsAtPath: @"/Library/PrivilegedHelperTools/org.eyebeam.SelfControl"]) { + if(![fileManager removeItemAtPath: @"/Library/PrivilegedHelperTools/org.eyebeam.SelfControl" error: nil]) { + NSLog(@"WARNING: Could not delete old helper binary."); + } + } + + SCXPCClient* xpc = [SCXPCClient new]; + + // use a semaphore to make sure the command-line tool doesn't exit + // while our blocks are still running + dispatch_semaphore_t installingBlockSema = dispatch_semaphore_create(0); + + NSLog(@"About to install helper tool"); + [xpc installDaemon:^(NSError * _Nonnull error) { + if (error != nil) { + NSLog(@"ERROR: Failed to install daemon with error %@", error); + exit(EX_SOFTWARE); + return; + } else { + // ok, the new helper tool is installed! refresh the connection, then it's time to start the block + [xpc refreshConnectionAndRun:^{ + NSLog(@"Refreshed connection and ready to start block!"); + [xpc startBlockWithControllingUID: controllingUID + blocklist: blocklist + isAllowlist: blockAsWhitelist + endDate: blockEndDate + blockSettings: blockSettings + reply:^(NSError * _Nonnull error) { + if (error != nil) { + NSLog(@"ERROR: Daemon failed to start block with error %@", error); + exit(EX_SOFTWARE); + return; + } + + NSLog(@"INFO: Block successfully added."); + dispatch_semaphore_signal(installingBlockSema); + }]; + }]; + } + }]; + + // obj-c could decide to run our things on the main thread, or not, so be careful + // but don't let us continue until the block has executed + if (![NSThread isMainThread]) { + dispatch_semaphore_wait(installingBlockSema, DISPATCH_TIME_FOREVER); + } else { + while (dispatch_semaphore_wait(installingBlockSema, DISPATCH_TIME_NOW)) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate date]]; + } + } + } + if([modeString isEqual: @"--remove"]) { + [SCSentry addBreadcrumb: @"CLI method --remove called" category: @"cli"]; + // So you think you can rid yourself of SelfControl just like that? + NSLog(@"INFO: Nice try."); + exit(EX_UNAVAILABLE); + } else if ([modeString isEqualToString: @"--print-settings"]) { + [SCSentry addBreadcrumb: @"CLI method --print-settings called" category: @"cli"]; + NSLog(@" - Printing SelfControl secured settings for debug: - "); + NSLog(@"%@", [settings dictionaryRepresentation]); + } else if ([modeString isEqualToString: @"--is-running"]) { + [SCSentry addBreadcrumb: @"CLI method --is-running called" category: @"cli"]; + BOOL blockIsRunning = [SCBlockUtilities anyBlockIsRunning]; + NSLog(@"%@", blockIsRunning ? @"YES" : @"NO"); + } else if ([modeString isEqualToString: @"--version"]) { + [SCSentry addBreadcrumb: @"CLI method --version called" category: @"cli"]; + NSLog(SELFCONTROL_VERSION_STRING); + } + + // final sync before we exit + exit(EXIT_SUCCESS); + } +} diff --git a/distribution-build.rb b/distribution-build.rb index ec0b04e3..e7f1c43b 100755 --- a/distribution-build.rb +++ b/distribution-build.rb @@ -102,14 +102,14 @@ def file_stats def get_dsa_signature puts "Generating DSA signature for archive at path #{@archive_path}" - puts "Command: #{SOURCE_FOLDER}/Pods/Sparkle/bin/old_dsa_scripts/sign_update \"#{@archive_path}\" /Volumes/SelfControl\ Keys\ and\ Secrets/Sparkle\ Signing\ Keys/dsa_priv.pem" - return `#{SOURCE_FOLDER}/Pods/Sparkle/bin/old_dsa_scripts/sign_update \"#{@archive_path}\" \"/Volumes/SelfControl\ Keys\ and\ Secrets/Sparkle\ Signing\ Keys/dsa_priv.pem\"`.chomp + puts "Command: #{SOURCE_FOLDER}/Sparkle/bin/old_dsa_scripts/sign_update \"#{@archive_path}\" /Volumes/SelfControl\ Keys\ and\ Secrets/Sparkle\ Signing\ Keys/dsa_priv.pem" + return `#{SOURCE_FOLDER}/Sparkle/bin/old_dsa_scripts/sign_update \"#{@archive_path}\" \"/Volumes/SelfControl\ Keys\ and\ Secrets/Sparkle\ Signing\ Keys/dsa_priv.pem\"`.chomp end def get_eddsa_signature_and_length_parameters puts "Generating edDSA signature parameters for archive at path #{@archive_path}" - puts "Command: #{SOURCE_FOLDER}/Pods/Sparkle/bin/sign_update \"#{@archive_path}\"" - return `#{SOURCE_FOLDER}/Pods/Sparkle/bin/sign_update \"#{@archive_path}\"`.chomp + puts "Command: #{SOURCE_FOLDER}/Sparkle/bin/sign_update \"#{@archive_path}\"" + return `#{SOURCE_FOLDER}/Sparkle/bin/sign_update \"#{@archive_path}\"`.chomp end def create_appcast_xml_snippet diff --git a/selfcontrol-cli-Info.plist b/selfcontrol-cli-Info.plist new file mode 100644 index 00000000..6cdc6da2 --- /dev/null +++ b/selfcontrol-cli-Info.plist @@ -0,0 +1,59 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + selfcontrol + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + selfcontrol + + CFBundleTypeIconFile + SelfControlBlocklist.icns + CFBundleTypeName + SelfControl Blocklist + CFBundleTypeRole + Editor + LSTypeIsPackage + + NSPersistentStoreTypeKey + Binary + + + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + SelfControlIcon + CFBundleIdentifier + org.eyebeam.selfcontrol-cli + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSHumanReadableCopyright + Free and open-source under the GPL. + SMPrivilegedExecutables + + org.eyebeam.selfcontrold + anchor apple generic and identifier "org.eyebeam.selfcontrold" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = L6W5L88KN7) + + +