diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md new file mode 100644 index 00000000..3bbb0252 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -0,0 +1,32 @@ +--- +name: "\U0001FAB2 Bug Report" +about: Report a reproducible bug in SelfControl +title: '' +labels: bug +assignees: '' + +--- + +# Summary + +In a sentence or two, what happened? + +# Background Info + +SelfControl version? +macOS version? +Do you use a VPN/proxy, and if so which one? +Has this _ever_ worked for you on a prior version of SelfControl? If so, when? +Any other special system setup we should know about? + +# How Do We Reproduce This Issue? + +Describe detailed steps that reliably make this issue show up. For example, "start a block containing 5 websites, click button X, then button Y" + +# What _Should_ Have Happened? + +What would this have looked like if it had worked? What did you expect to happen? + +# What Actually Happened Instead? + +What did you actually see? e.g. what error messages popped up, what window is showing what text, etc. diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md new file mode 100644 index 00000000..09a9c5e4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---feature-request.md @@ -0,0 +1,20 @@ +--- +name: "\U0001F9E0 Feature Request" +about: Suggest a new feature or improvement for SelfControl +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..f0dc09f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: ⛔️ SelfControl Is Still Blocking Sites After The Timer Expired + url: https://github.com/SelfControlApp/selfcontrol/wiki/Help!-SelfControl-is-still-blocking-sites-even-after-the-block-should-have-ended! + about: Follow these directions to remove your block and restore access to blocked websites. + - name: 🙋‍♀️ General Comment, Question, or Concern + url: https://github.com/SelfControlApp/selfcontrol/discussions + about: Use our discussion board to share tips and tricks, ask questions, or kvetch when SelfControl doesn't work diff --git a/.github/docs/screenshot.png b/.github/docs/screenshot.png new file mode 100644 index 00000000..74e3019b Binary files /dev/null and b/.github/docs/screenshot.png differ diff --git a/AppController.h b/AppController.h index 4db32f47..b613d7cf 100755 --- a/AppController.h +++ b/AppController.h @@ -30,11 +30,12 @@ #import #import #import "SCSettings.h" +#import "SCDurationSlider.h" // The main controller for the SelfControl app, which includes several methods // to handle command flow and acts as delegate for the initial window. @interface AppController : NSObject { - IBOutlet NSSlider* blockDurationSlider_; + IBOutlet SCDurationSlider* blockDurationSlider_; IBOutlet NSTextField* blockSliderTimeDisplayLabel_; IBOutlet NSTextField* blocklistTeaserLabel_; IBOutlet NSButton* submitButton_; @@ -127,4 +128,7 @@ // opens the SelfControl FAQ in the default browser - (IBAction)openFAQ:(id)sender; +// opens the SelfControl Support Hub in the default browser +- (IBAction)openSupportHub:(id)sender; + @end diff --git a/AppController.m b/AppController.m index edde00df..242bdabf 100755 --- a/AppController.m +++ b/AppController.m @@ -29,7 +29,6 @@ #import "SCSettings.h" #import #import "SCXPCClient.h" -#import "HostFileBlocker.h" #import "SCBlockFileReaderWriter.h" #import "SCUIUtilities.h" #import @@ -69,16 +68,13 @@ - (IBAction)updateTimeSliderDisplay:(id)sender { // if the duration is larger than we can display on our slider // chop it down to our max display value so the user doesn't // accidentally start a much longer block than intended - if (numMinutes > blockDurationSlider_.maxValue) { - [self setDefaultsBlockDurationOnMainThread: @(floor(blockDurationSlider_.maxValue))]; + if (numMinutes > blockDurationSlider_.maxDuration) { + [self setDefaultsBlockDurationOnMainThread: @(floor(blockDurationSlider_.maxDuration))]; numMinutes = [defaults_ integerForKey: @"BlockDuration"]; } - // Time-display code cleaned up thanks to the contributions of many users + blockSliderTimeDisplayLabel_.stringValue = blockDurationSlider_.durationDescription; - NSString* timeString = [SCUIUtilities timeSliderDisplayStringFromNumberOfMinutes:numMinutes]; - - [blockSliderTimeDisplayLabel_ setStringValue:timeString]; [submitButton_ setEnabled: (numMinutes > 0) && ([[defaults_ arrayForKey: @"Blocklist"] count] > 0)]; } @@ -105,24 +101,67 @@ - (IBAction)addBlock:(id)sender { NSAlert* networkUnavailableAlert = [[NSAlert alloc] init]; [networkUnavailableAlert setMessageText: NSLocalizedString(@"No network connection detected", "No network connection detected message")]; [networkUnavailableAlert setInformativeText:NSLocalizedString(@"A block cannot be started without a working network connection. You can override this setting in Preferences.", @"Message when network connection is unavailable")]; - [networkUnavailableAlert addButtonWithTitle: NSLocalizedString(@"Cancel", "Cancel button")]; - [networkUnavailableAlert addButtonWithTitle: NSLocalizedString(@"Network Diagnostics...", @"Network Diagnostics button")]; - if([networkUnavailableAlert runModal] == NSAlertFirstButtonReturn) - return; - - // If the user selected Network Diagnostics launch an assisant to help them. - // apple.com is an arbitrary host chosen to pass to Network Diagnostics. - CFURLRef url = CFURLCreateWithString(NULL, CFSTR("http://apple.com"), NULL); - CFNetDiagnosticRef diagRef = CFNetDiagnosticCreateWithURL(kCFAllocatorDefault, url); - CFNetDiagnosticDiagnoseProblemInteractively(diagRef); + [networkUnavailableAlert addButtonWithTitle: NSLocalizedString(@"OK", "OK button")]; + [networkUnavailableAlert runModal]; return; } + // cancel if we pop up a warning about the super long block, and the user decides to cancel + if (![self showLongBlockWarningsIfNecessary]) { + return; + } + [timerWindowController_ resetStrikes]; [NSThread detachNewThreadSelector: @selector(installBlock) toTarget: self withObject: nil]; } +// returns YES if we should continue with the block, NO if we should cancel it +- (BOOL)showLongBlockWarningsIfNecessary { + // all UI stuff MUST be done on the main thread + if (![NSThread isMainThread]) { + __block BOOL retVal = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ + retVal = [self showLongBlockWarningsIfNecessary]; + }); + return retVal; + } + + NSString* LONG_BLOCK_SUPPRESSION_KEY = @"SuppressLongBlockWarning"; + int LONG_BLOCK_THRESHOLD_MINS = 2880; // 2 days + int FIRST_TIME_LONG_BLOCK_THRESHOLD_MINS = 480; // 8 hours + + BOOL isFirstBlock = ![defaults_ boolForKey: @"FirstBlockStarted"]; + int blockDuration = [[self->defaults_ valueForKey: @"BlockDuration"] intValue]; + + BOOL showLongBlockWarning = blockDuration >= LONG_BLOCK_THRESHOLD_MINS || (isFirstBlock && blockDuration >= FIRST_TIME_LONG_BLOCK_THRESHOLD_MINS); + if (!showLongBlockWarning) return YES; + + // if they don't want warnings, they don't get warnings. their funeral 💀 + if ([self->defaults_ boolForKey: LONG_BLOCK_SUPPRESSION_KEY]) { + return YES; + } + + NSAlert* alert = [[NSAlert alloc] init]; + alert.messageText = NSLocalizedString(@"That's a long block!", "Long block warning title"); + alert.informativeText = [NSString stringWithFormat: NSLocalizedString(@"Remember that once you start the block, you can't turn it back off until the timer expires in %@ - even if you accidentally blocked a site you need. Consider starting a shorter block first, to test your list and make sure everything's working properly.", @"Long block warning message"), [SCDurationSlider timeSliderDisplayStringFromNumberOfMinutes: blockDuration]]; + [alert addButtonWithTitle: NSLocalizedString(@"Cancel", @"Button to cancel a long block")]; + [alert addButtonWithTitle: NSLocalizedString(@"Start Block Anyway", "Button to start a long block despite warnings")]; + alert.showsSuppressionButton = YES; + + NSModalResponse modalResponse = [alert runModal]; + if (alert.suppressionButton.state == NSControlStateValueOn) { + // no more warnings, they say + [self->defaults_ setBool: YES forKey: LONG_BLOCK_SUPPRESSION_KEY]; + } + if (modalResponse == NSAlertFirstButtonReturn) { + return NO; + } + + return YES; +} + + - (void)refreshUserInterface { // UI updates are for the main thread only! if (![NSThread isMainThread]) { @@ -146,6 +185,9 @@ - (void)refreshUserInterface { [self showTimerWindow]; [initialWindow_ close]; [self closeDomainList]; + + // apparently, a block is running, so make sure FirstBlockStarted is true + [defaults_ setBool: YES forKey: @"FirstBlockStarted"]; } } else { // block is off if(blockWasOn) { // if we just switched states to off... @@ -164,6 +206,15 @@ - (void)refreshUserInterface { // make sure the dock badge is cleared [[NSApp dockTile] setBadgeLabel: nil]; + // send a notification letting the user know the block ended + // TODO: make this sent from a background process so it shows if app is closed + // (but we can't send it from the selfcontrold process, because it's running as root) + NSUserNotificationCenter* userNoteCenter = [NSUserNotificationCenter defaultUserNotificationCenter]; + NSUserNotification* endedNote = [NSUserNotification new]; + endedNote.title = @"Your SelfControl block has ended!"; + endedNote.informativeText = @"All sites are now accessible."; + [userNoteCenter deliverNotification: endedNote]; + [self closeTimerWindow]; } @@ -181,11 +232,11 @@ - (void)refreshUserInterface { if(!self.addingBlock) { [blockDurationSlider_ setEnabled: YES]; [editBlocklistButton_ setEnabled: YES]; - [submitButton_ setTitle: NSLocalizedString(@"Start", @"Start button")]; + [submitButton_ setTitle: NSLocalizedString(@"Start Block", @"Start button")]; } else { [blockDurationSlider_ setEnabled: NO]; [editBlocklistButton_ setEnabled: NO]; - [submitButton_ setTitle: NSLocalizedString(@"Loading", @"Loading button")]; + [submitButton_ setTitle: NSLocalizedString(@"Starting Block", @"Starting Block button")]; } // if block's off, and we haven't shown it yet, show the first-time modal @@ -225,12 +276,18 @@ - (void)handleConfigurationChangedNotification { // if our configuration changed, we should assume the settings may have changed [[SCSettings sharedSettings] reloadSettings]; + // clean out empty strings from the defaults blocklist (they can end up there occasionally due to UI glitches etc) + // note we don't screw with the actively running blocklist - that should've been cleaned before it started anyway + NSArray* cleanedBlocklist = [SCMiscUtilities cleanBlocklist: [defaults_ arrayForKey: @"Blocklist"]]; + [defaults_ setObject: cleanedBlocklist forKey: @"Blocklist"]; + // update our blocklist teaser string blocklistTeaserLabel_.stringValue = [SCUIUtilities blockTeaserStringWithMaxLength: 60]; // let the domain list know! if (domainListWindowController_ != nil) { domainListWindowController_.readOnly = [SCUIUtilities blockIsRunning]; + [domainListWindowController_ refreshDomainList]; } // let the timer window know! @@ -354,38 +411,15 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { blockIsOn = ![SCUIUtilities blockIsRunning]; // Change block duration slider for hidden user defaults settings - long numTickMarks = ([defaults_ integerForKey: @"MaxBlockLength"] / [defaults_ integerForKey: @"BlockLengthInterval"]) + 1; - [blockDurationSlider_ setMaxValue: [defaults_ integerForKey: @"MaxBlockLength"]]; - [blockDurationSlider_ setNumberOfTickMarks: numTickMarks]; - - [NSValueTransformer registerValueTransformerWithName: @"BlockDurationSliderTransformer" - transformedValueClass: [NSNumber class] - returningTransformedValueWithBlock:^id _Nonnull(id _Nonnull value) { - // if it's not a number or convertable to one, IDK man - if (![value respondsToSelector: @selector(intValue)]) return @0; - - // instead of having 0 as the first option (who would ever want to start a 0-minute block?) - // we make it 1 minute, which is super handy for testing blocklists - // (of course, if the next tick mark is 1 minute anyway, we can skip that) - if ([value intValue] == 0 && [self->defaults_ integerForKey: @"BlockLengthInterval"] != 1) { - return @1; - } - - return value; - }]; - [blockDurationSlider_ bind: @"value" - toObject: [NSUserDefaultsController sharedUserDefaultsController] - withKeyPath: @"values.BlockDuration" - options: @{ - NSContinuouslyUpdatesValueBindingOption: @YES, - NSValueTransformerNameBindingOption: @"BlockDurationSliderTransformer" - }]; + blockDurationSlider_.maxDuration = [defaults_ integerForKey: @"MaxBlockLength"]; + [blockDurationSlider_ bindDurationToObject: [NSUserDefaultsController sharedUserDefaultsController] + keyPath: @"values.BlockDuration"]; blocklistTeaserLabel_.stringValue = [SCUIUtilities blockTeaserStringWithMaxLength: 60]; [self refreshUserInterface]; - NSOperatingSystemVersion minRequiredVersion = (NSOperatingSystemVersion){10,10,0}; // Mountain Lion + NSOperatingSystemVersion minRequiredVersion = (NSOperatingSystemVersion){10,10,0}; // Yosemite NSString* minRequiredVersionString = @"10.10 (Yosemite)"; if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: minRequiredVersion]) { NSLog(@"ERROR: Unsupported version for SelfControl"); @@ -458,8 +492,11 @@ - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock { if (cleanedEntries.count == 0) return; for (NSUInteger i = 0; i < cleanedEntries.count; i++) { - NSString* entry = cleanedEntries[i]; - [list addObject: entry]; + NSString* entry = cleanedEntries[i]; + // don't add duplicate entries + if (![list containsObject: entry]) { + [list addObject: entry]; + } } [defaults_ setValue: list forKey: @"Blocklist"]; @@ -482,19 +519,8 @@ - (void)addToBlockList:(NSString*)host lock:(NSLock*)lock { NSAlert* networkUnavailableAlert = [[NSAlert alloc] init]; [networkUnavailableAlert setMessageText: NSLocalizedString(@"No network connection detected", "No network connection detected message")]; [networkUnavailableAlert setInformativeText:NSLocalizedString(@"A block cannot be started without a working network connection. You can override this setting in Preferences.", @"Message when network connection is unavailable")]; - [networkUnavailableAlert addButtonWithTitle: NSLocalizedString(@"Cancel", "Cancel button")]; - [networkUnavailableAlert addButtonWithTitle: NSLocalizedString(@"Network Diagnostics...", @"Network Diagnostics button")]; - if([networkUnavailableAlert runModal] == NSAlertFirstButtonReturn) { - // User clicked cancel - return; - } - - // If the user selected Network Diagnostics, launch an assisant to help them. - // apple.com is an arbitrary host chosen to pass to Network Diagnostics. - CFURLRef url = CFURLCreateWithString(NULL, CFSTR("http://apple.com"), NULL); - CFNetDiagnosticRef diagRef = CFNetDiagnosticCreateWithURL(kCFAllocatorDefault, url); - CFNetDiagnosticDiagnoseProblemInteractively(diagRef); - + [networkUnavailableAlert addButtonWithTitle: NSLocalizedString(@"OK", "OK button")]; + [networkUnavailableAlert runModal]; return; } @@ -558,7 +584,13 @@ - (void)installBlock { [SCSentry addBreadcrumb: @"App running installBlock method" category:@"app"]; @autoreleasepool { self.addingBlock = true; + + // if there are any ongoing edits in the domain list, make sure they make it in + if (domainListWindowController_ != nil) { + [domainListWindowController_ refreshDomainList]; + } [self refreshUserInterface]; + [self.xpc installDaemon:^(NSError * _Nonnull error) { if (error != nil) { [SCUIUtilities presentError: error]; @@ -711,7 +743,7 @@ - (IBAction)save:(id)sender { error: &err]; if (err != nil) { - NSError* displayErr = [SCErr errorWithCode: 105 subDescription: err.localizedDescription]; + NSError* displayErr = [SCErr errorWithCode: 101 subDescription: err.localizedDescription]; [SCSentry captureError: displayErr]; NSBeep(); [SCUIUtilities presentError: displayErr]; @@ -734,14 +766,8 @@ - (BOOL)openSavedBlockFileAtURL:(NSURL*)fileURL { 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]; - } + // send a notification so the domain list (etc) updates + [[NSNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification" object: self]; [self refreshUserInterface]; return YES; @@ -770,4 +796,11 @@ - (IBAction)openFAQ:(id)sender { [[NSWorkspace sharedWorkspace] openURL: url]; } +- (IBAction)openSupportHub:(id)sender { + [SCSentry addBreadcrumb: @"Opened SelfControl Support Hub" category:@"app"]; + NSURL *url=[NSURL URLWithString: @"https://selfcontrolapp.com/support"]; + [[NSWorkspace sharedWorkspace] openURL: url]; +} + + @end diff --git a/Base.lproj/DomainList.xib b/Base.lproj/DomainList.xib index 7580c0ec..88ae01ea 100755 --- a/Base.lproj/DomainList.xib +++ b/Base.lproj/DomainList.xib @@ -1,8 +1,8 @@ - + - + @@ -30,7 +30,7 @@ - + - + - + - - + + YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T diff --git a/Base.lproj/FirstTime.xib b/Base.lproj/FirstTime.xib index d7fd3fb6..1bb7cfbc 100644 --- a/Base.lproj/FirstTime.xib +++ b/Base.lproj/FirstTime.xib @@ -1,8 +1,8 @@ - + - + @@ -13,11 +13,11 @@ - + - + @@ -25,14 +25,15 @@ - + - + - + + @@ -41,6 +42,7 @@ Cg + @@ -86,7 +88,7 @@ Cg - + @@ -135,7 +137,7 @@ Cg Cg - + @@ -184,6 +186,7 @@ Cg Cg + @@ -231,6 +234,7 @@ Cg Quick Start Guide + @@ -281,6 +285,7 @@ Cg Cg + @@ -328,6 +333,7 @@ Cg 1. + @@ -375,8 +381,9 @@ Cg - + + @@ -428,6 +435,7 @@ Cg and add the domain names of your most distracting websites -- for example, "facebook.com". Or use the Import function to add a pre-made list of websites. 2. + @@ -477,6 +485,7 @@ Cg + @@ -525,9 +534,10 @@ Cg - to decide how long you want to block these websites for. You can change it from 15 minutes all the way up to a whole day. + to decide how long you want to block these websites for. You can change it from 1 minute all the way up to a whole day. 3. + @@ -575,8 +585,9 @@ Cg - + + @@ -628,6 +639,7 @@ Cg , type your password to start the block, and get to work! + @@ -680,6 +692,7 @@ Cg CgoKCg + @@ -729,6 +742,7 @@ CgoKCg Cg + @@ -775,6 +789,7 @@ Cg + @@ -820,6 +835,7 @@ Cg + @@ -866,6 +882,7 @@ Cg + @@ -913,7 +930,6 @@ Cg - diff --git a/Base.lproj/MainMenu.xib b/Base.lproj/MainMenu.xib index 5cac760f..9acd0eda 100755 --- a/Base.lproj/MainMenu.xib +++ b/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -219,7 +219,13 @@ - + + + + + + + @@ -231,20 +237,20 @@ - + - + - + - + @@ -252,11 +258,11 @@ - - - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + - - - + - - - + + - + + @@ -108,33 +127,34 @@ - - + + + - + - + - - + + - + @@ -142,8 +162,8 @@ + + + + + + + + + + + + + + + + + + + + + - + - - - - + - - - - - + + + + - + - - + + - + - - - -YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T -S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T -U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN -EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdMPJSYnKBRf -EBROU1RJRkZSZXByZXNlbnRhdGlvbl8QGU5TSW50ZXJuYWxMYXlvdXREaXJlY3Rpb26ACIAHTxESbE1N -ACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEG -AAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEW -AAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFT -AAMAAAACAAEAAYdzAAcAABGcAAAA0AAAAAAAABGcYXBwbAIAAABtbnRyR1JBWVhZWiAH3AAIABcADwAu -AA9hY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAHlkc2NtAAABPAAA -CBpjcHJ0AAAJWAAAACN3dHB0AAAJfAAAABRrVFJDAAAJkAAACAxkZXNjAAAAAAAAAB9HZW5lcmljIEdy -YXkgR2FtbWEgMi4yIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNr -U0sAAAAuAAABhGRhREsAAAA6AAABsmNhRVMAAAA4AAAB7HZpVk4AAABAAAACJHB0QlIAAABKAAACZHVr -VUEAAAAsAAACrmZyRlUAAAA+AAAC2mh1SFUAAAA0AAADGHpoVFcAAAAaAAADTGtvS1IAAAAiAAADZm5i -Tk8AAAA6AAADiGNzQ1oAAAAoAAADwmhlSUwAAAAkAAAD6nJvUk8AAAAqAAAEDmRlREUAAABOAAAEOGl0 -SVQAAABOAAAEhnN2U0UAAAA4AAAE1HpoQ04AAAAaAAAFDGphSlAAAAAmAAAFJmVsR1IAAAAqAAAFTHB0 -UE8AAABSAAAFdm5sTkwAAABAAAAFyGVzRVMAAABMAAAGCHRoVEgAAAAyAAAGVHRyVFIAAAAkAAAGhmZp -RkkAAABGAAAGqmhySFIAAAA+AAAG8HBsUEwAAABKAAAHLmFyRUcAAAAsAAAHeHJ1UlUAAAA6AAAHpGVu -VVMAAAA8AAAH3gBWAWEAZQBvAGIAZQBjAG4A4QAgAHMAaQB2AOEAIABnAGEAbQBhACAAMgAsADIARwBl -AG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQAtAHAAcgBvAGYAaQBsAEcAYQBt -AG0AYQAgAGQAZQAgAGcAcgBpAHMAbwBzACAAZwBlAG4A6AByAGkAYwBhACAAMgAuADIAQx6lAHUAIABo -AOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1AG4AZwAgAEcAYQBtAG0AYQAgADIALgAyAFAAZQBy -AGYAaQBsACAARwBlAG4A6QByAGkAYwBvACAAZABhACAARwBhAG0AYQAgAGQAZQAgAEMAaQBuAHoAYQBz -ACAAMgAsADIEFwQwBDMEMAQ7BEwEPQQwACAARwByAGEAeQAtBDMEMAQ8BDAAIAAyAC4AMgBQAHIAbwBm -AGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAZwByAGkAcwAgAGcAYQBtAG0AYQAgADIALAAyAMEAbAB0 -AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABnAGEAbQBtAGEAIAAyAC4AMpAadShwcJaOUUlepgAy -AC4AMoJyX2ljz4/wx3y8GAAg1ozAyQAgrBC5yAAgADIALgAyACDVBLhc0wzHfABHAGUAbgBlAHIAaQBz -AGsAIABnAHIA5QAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8AZgBpAGwATwBiAGUAYwBuAOEAIAFh -AGUAZADhACAAZwBhAG0AYQAgADIALgAyBdIF0AXeBdQAIAXQBeQF1QXoACAF2wXcBdwF2QAgADIALgAy -AEcAYQBtAGEAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMBAwAgADIALAAyAEEAbABsAGcAZQBtAGUAaQBu -AGUAcwAgAEcAcgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBmAGkAbAAgAEcAYQBtAG0AYQAgADIALAAy -AFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwAgAGQAZQBsAGwAYQAg -AGcAYQBtAG0AYQAgADIALAAyAEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAMgAsADIAIABnAGEAbQBt -AGEAcAByAG8AZgBpAGxmbpAacHBepnz7ZXAAMgAuADJjz4/wZYdO9k4AgiwwsDDsMKQwrDDzMN4AIAAy -AC4AMgAgMNcw7TDVMKEwpDDrA5MDtQO9A7kDugPMACADkwO6A8EDuQAgA5MDrAO8A7wDsQAgADIALgAy -AFAAZQByAGYAaQBsACAAZwBlAG4A6QByAGkAYwBvACAAZABlACAAYwBpAG4AegBlAG4AdABvAHMAIABk -AGEAIABHAGEAbQBtAGEAIAAyACwAMgBBAGwAZwBlAG0AZQBlAG4AIABnAHIAaQBqAHMAIABnAGEAbQBt -AGEAIAAyACwAMgAtAHAAcgBvAGYAaQBlAGwAUABlAHIAZgBpAGwAIABnAGUAbgDpAHIAaQBjAG8AIABk -AGUAIABnAGEAbQBtAGEAIABkAGUAIABnAHIAaQBzAGUAcwAgADIALAAyDiMOMQ4HDioONQ5BDgEOIQ4h -DjIOQA4BDiMOIg5MDhcOMQ5IDicORA4bACAAMgAuADIARwBlAG4AZQBsACAARwByAGkAIABHAGEAbQBh -ACAAMgAsADIAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAG4AIABnAGEAbQBtAGEAIAAyACwAMgAg -AC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0AawBpACAARwByAGEAeQAgAEcAYQBtAG0AYQAg -ADIALgAyACAAcAByAG8AZgBpAGwAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABz -AHoAYQByAG8BWwBjAGkAIABnAGEAbQBtAGEAIAAyACwAMgY6BicGRQYnACAAMgAuADIAIAZEBkgGRgAg -BjEGRQYnBi8GSgAgBjkGJwZFBB4EMQRJBDAETwAgBEEENQRABDAETwAgBDMEMAQ8BDwEMAAgADIALAAy -AC0EPwRABD4ERAQ4BDsETABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAEcAYQBtAG0AYQAgADIALgAy -ACAAUAByAG8AZgBpAGwAZQAAdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxMgAAWFlaIAAA -AAAAAPNRAAEAAAABFsxjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBP -AFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADl -AOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGp -AbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2 -AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQT -BCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXF -BdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfS -B+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9 -ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0N -DSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBD -EGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPl -FAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3 -GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7 -HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1 -IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo -JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizX -LQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNG -M38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2 -OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGs -Qe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mp -SfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIx -UnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtF -W5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTp -ZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8e -b3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn -ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH -hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/ -kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3S -nkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sC -q3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR -uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dB -x7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV -1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN -5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt -9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//9IrLC0uWiRjbGFzc25hbWVYJGNsYXNzZXNf -EBBOU0JpdG1hcEltYWdlUmVwoy0vMFpOU0ltYWdlUmVwWE5TT2JqZWN00issMjNXTlNBcnJheaIyMNIr -LDU2Xk5TTXV0YWJsZUFycmF5ozUyMNM4OQ86OzxXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzS -Kyw+P1dOU0NvbG9yoj4w0issQUJXTlNJbWFnZaJBMAAIABEAGgAkACkAMgA3AEkATABRAFMAYgBoAHUA -fACLAJIAnwCmAK4AsACyALQAuQC7AL0AxADJANQA1gDYANoA3wDiAOQA5gDoAO8BBgEiASQBJhOWE5sT -phOvE8ITxhPRE9oT3xPnE+oT7xP+FAIUCRQRFB4UIxQlFCcULBQ0FDcUPBREAAAAAAAAAgEAAAAAAAAA -QwAAAAAAAAAAAAAAAAAAFEc - - - - -YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T -S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T -U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN -EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdMPJSYnKBRf -EBROU1RJRkZSZXByZXNlbnRhdGlvbl8QGU5TSW50ZXJuYWxMYXlvdXREaXJlY3Rpb26ACIAHTxESbE1N -ACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEG -AAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEW -AAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFT -AAMAAAACAAEAAYdzAAcAABGcAAAA0AAAAAAAABGcYXBwbAIAAABtbnRyR1JBWVhZWiAH3AAIABcADwAu -AA9hY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAHlkc2NtAAABPAAA -CBpjcHJ0AAAJWAAAACN3dHB0AAAJfAAAABRrVFJDAAAJkAAACAxkZXNjAAAAAAAAAB9HZW5lcmljIEdy -YXkgR2FtbWEgMi4yIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNr -U0sAAAAuAAABhGRhREsAAAA6AAABsmNhRVMAAAA4AAAB7HZpVk4AAABAAAACJHB0QlIAAABKAAACZHVr -VUEAAAAsAAACrmZyRlUAAAA+AAAC2mh1SFUAAAA0AAADGHpoVFcAAAAaAAADTGtvS1IAAAAiAAADZm5i -Tk8AAAA6AAADiGNzQ1oAAAAoAAADwmhlSUwAAAAkAAAD6nJvUk8AAAAqAAAEDmRlREUAAABOAAAEOGl0 -SVQAAABOAAAEhnN2U0UAAAA4AAAE1HpoQ04AAAAaAAAFDGphSlAAAAAmAAAFJmVsR1IAAAAqAAAFTHB0 -UE8AAABSAAAFdm5sTkwAAABAAAAFyGVzRVMAAABMAAAGCHRoVEgAAAAyAAAGVHRyVFIAAAAkAAAGhmZp -RkkAAABGAAAGqmhySFIAAAA+AAAG8HBsUEwAAABKAAAHLmFyRUcAAAAsAAAHeHJ1UlUAAAA6AAAHpGVu -VVMAAAA8AAAH3gBWAWEAZQBvAGIAZQBjAG4A4QAgAHMAaQB2AOEAIABnAGEAbQBhACAAMgAsADIARwBl -AG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQAtAHAAcgBvAGYAaQBsAEcAYQBt -AG0AYQAgAGQAZQAgAGcAcgBpAHMAbwBzACAAZwBlAG4A6AByAGkAYwBhACAAMgAuADIAQx6lAHUAIABo -AOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1AG4AZwAgAEcAYQBtAG0AYQAgADIALgAyAFAAZQBy -AGYAaQBsACAARwBlAG4A6QByAGkAYwBvACAAZABhACAARwBhAG0AYQAgAGQAZQAgAEMAaQBuAHoAYQBz -ACAAMgAsADIEFwQwBDMEMAQ7BEwEPQQwACAARwByAGEAeQAtBDMEMAQ8BDAAIAAyAC4AMgBQAHIAbwBm -AGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAZwByAGkAcwAgAGcAYQBtAG0AYQAgADIALAAyAMEAbAB0 -AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABnAGEAbQBtAGEAIAAyAC4AMpAadShwcJaOUUlepgAy -AC4AMoJyX2ljz4/wx3y8GAAg1ozAyQAgrBC5yAAgADIALgAyACDVBLhc0wzHfABHAGUAbgBlAHIAaQBz -AGsAIABnAHIA5QAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8AZgBpAGwATwBiAGUAYwBuAOEAIAFh -AGUAZADhACAAZwBhAG0AYQAgADIALgAyBdIF0AXeBdQAIAXQBeQF1QXoACAF2wXcBdwF2QAgADIALgAy -AEcAYQBtAGEAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMBAwAgADIALAAyAEEAbABsAGcAZQBtAGUAaQBu -AGUAcwAgAEcAcgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBmAGkAbAAgAEcAYQBtAG0AYQAgADIALAAy -AFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwAgAGQAZQBsAGwAYQAg -AGcAYQBtAG0AYQAgADIALAAyAEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAMgAsADIAIABnAGEAbQBt -AGEAcAByAG8AZgBpAGxmbpAacHBepnz7ZXAAMgAuADJjz4/wZYdO9k4AgiwwsDDsMKQwrDDzMN4AIAAy -AC4AMgAgMNcw7TDVMKEwpDDrA5MDtQO9A7kDugPMACADkwO6A8EDuQAgA5MDrAO8A7wDsQAgADIALgAy -AFAAZQByAGYAaQBsACAAZwBlAG4A6QByAGkAYwBvACAAZABlACAAYwBpAG4AegBlAG4AdABvAHMAIABk -AGEAIABHAGEAbQBtAGEAIAAyACwAMgBBAGwAZwBlAG0AZQBlAG4AIABnAHIAaQBqAHMAIABnAGEAbQBt -AGEAIAAyACwAMgAtAHAAcgBvAGYAaQBlAGwAUABlAHIAZgBpAGwAIABnAGUAbgDpAHIAaQBjAG8AIABk -AGUAIABnAGEAbQBtAGEAIABkAGUAIABnAHIAaQBzAGUAcwAgADIALAAyDiMOMQ4HDioONQ5BDgEOIQ4h -DjIOQA4BDiMOIg5MDhcOMQ5IDicORA4bACAAMgAuADIARwBlAG4AZQBsACAARwByAGkAIABHAGEAbQBh -ACAAMgAsADIAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAG4AIABnAGEAbQBtAGEAIAAyACwAMgAg -AC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0AawBpACAARwByAGEAeQAgAEcAYQBtAG0AYQAg -ADIALgAyACAAcAByAG8AZgBpAGwAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABz -AHoAYQByAG8BWwBjAGkAIABnAGEAbQBtAGEAIAAyACwAMgY6BicGRQYnACAAMgAuADIAIAZEBkgGRgAg -BjEGRQYnBi8GSgAgBjkGJwZFBB4EMQRJBDAETwAgBEEENQRABDAETwAgBDMEMAQ8BDwEMAAgADIALAAy -AC0EPwRABD4ERAQ4BDsETABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAEcAYQBtAG0AYQAgADIALgAy -ACAAUAByAG8AZgBpAGwAZQAAdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxMgAAWFlaIAAA -AAAAAPNRAAEAAAABFsxjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBP -AFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADl -AOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGp -AbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2 -AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQT -BCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXF -BdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfS -B+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9 -ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0N -DSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBD -EGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPl -FAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3 -GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7 -HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1 -IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo -JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizX -LQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNG -M38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2 -OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGs -Qe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mp -SfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIx -UnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtF -W5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTp -ZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8e -b3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn -ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH -hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/ -kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3S -nkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sC -q3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR -uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dB -x7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV -1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN -5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt -9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//9IrLC0uWiRjbGFzc25hbWVYJGNsYXNzZXNf -EBBOU0JpdG1hcEltYWdlUmVwoy0vMFpOU0ltYWdlUmVwWE5TT2JqZWN00issMjNXTlNBcnJheaIyMNIr -LDU2Xk5TTXV0YWJsZUFycmF5ozUyMNM4OQ86OzxXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzS -Kyw+P1dOU0NvbG9yoj4w0issQUJXTlNJbWFnZaJBMAAIABEAGgAkACkAMgA3AEkATABRAFMAYgBoAHUA -fACLAJIAnwCmAK4AsACyALQAuQC7AL0AxADJANQA1gDYANoA3wDiAOQA5gDoAO8BBgEiASQBJhOWE5sT -phOvE8ITxhPRE9oT3xPnE+oT7xP+FAIUCRQRFB4UIxQlFCcULBQ0FDcUPBREAAAAAAAAAgEAAAAAAAAA -QwAAAAAAAAAAAAAAAAAAFEc - - - - -YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T -S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T -U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN -EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdMPJSYnKBRf -EBROU1RJRkZSZXByZXNlbnRhdGlvbl8QGU5TSW50ZXJuYWxMYXlvdXREaXJlY3Rpb26ACIAHTxESbE1N -ACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEG -AAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEW -AAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFT -AAMAAAACAAEAAYdzAAcAABGcAAAA0AAAAAAAABGcYXBwbAIAAABtbnRyR1JBWVhZWiAH3AAIABcADwAu -AA9hY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAHlkc2NtAAABPAAA -CBpjcHJ0AAAJWAAAACN3dHB0AAAJfAAAABRrVFJDAAAJkAAACAxkZXNjAAAAAAAAAB9HZW5lcmljIEdy -YXkgR2FtbWEgMi4yIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNr -U0sAAAAuAAABhGRhREsAAAA6AAABsmNhRVMAAAA4AAAB7HZpVk4AAABAAAACJHB0QlIAAABKAAACZHVr -VUEAAAAsAAACrmZyRlUAAAA+AAAC2mh1SFUAAAA0AAADGHpoVFcAAAAaAAADTGtvS1IAAAAiAAADZm5i -Tk8AAAA6AAADiGNzQ1oAAAAoAAADwmhlSUwAAAAkAAAD6nJvUk8AAAAqAAAEDmRlREUAAABOAAAEOGl0 -SVQAAABOAAAEhnN2U0UAAAA4AAAE1HpoQ04AAAAaAAAFDGphSlAAAAAmAAAFJmVsR1IAAAAqAAAFTHB0 -UE8AAABSAAAFdm5sTkwAAABAAAAFyGVzRVMAAABMAAAGCHRoVEgAAAAyAAAGVHRyVFIAAAAkAAAGhmZp -RkkAAABGAAAGqmhySFIAAAA+AAAG8HBsUEwAAABKAAAHLmFyRUcAAAAsAAAHeHJ1UlUAAAA6AAAHpGVu -VVMAAAA8AAAH3gBWAWEAZQBvAGIAZQBjAG4A4QAgAHMAaQB2AOEAIABnAGEAbQBhACAAMgAsADIARwBl -AG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQAtAHAAcgBvAGYAaQBsAEcAYQBt -AG0AYQAgAGQAZQAgAGcAcgBpAHMAbwBzACAAZwBlAG4A6AByAGkAYwBhACAAMgAuADIAQx6lAHUAIABo -AOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1AG4AZwAgAEcAYQBtAG0AYQAgADIALgAyAFAAZQBy -AGYAaQBsACAARwBlAG4A6QByAGkAYwBvACAAZABhACAARwBhAG0AYQAgAGQAZQAgAEMAaQBuAHoAYQBz -ACAAMgAsADIEFwQwBDMEMAQ7BEwEPQQwACAARwByAGEAeQAtBDMEMAQ8BDAAIAAyAC4AMgBQAHIAbwBm -AGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAZwByAGkAcwAgAGcAYQBtAG0AYQAgADIALAAyAMEAbAB0 -AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABnAGEAbQBtAGEAIAAyAC4AMpAadShwcJaOUUlepgAy -AC4AMoJyX2ljz4/wx3y8GAAg1ozAyQAgrBC5yAAgADIALgAyACDVBLhc0wzHfABHAGUAbgBlAHIAaQBz -AGsAIABnAHIA5QAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8AZgBpAGwATwBiAGUAYwBuAOEAIAFh -AGUAZADhACAAZwBhAG0AYQAgADIALgAyBdIF0AXeBdQAIAXQBeQF1QXoACAF2wXcBdwF2QAgADIALgAy -AEcAYQBtAGEAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMBAwAgADIALAAyAEEAbABsAGcAZQBtAGUAaQBu -AGUAcwAgAEcAcgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBmAGkAbAAgAEcAYQBtAG0AYQAgADIALAAy -AFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwAgAGQAZQBsAGwAYQAg -AGcAYQBtAG0AYQAgADIALAAyAEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAMgAsADIAIABnAGEAbQBt -AGEAcAByAG8AZgBpAGxmbpAacHBepnz7ZXAAMgAuADJjz4/wZYdO9k4AgiwwsDDsMKQwrDDzMN4AIAAy -AC4AMgAgMNcw7TDVMKEwpDDrA5MDtQO9A7kDugPMACADkwO6A8EDuQAgA5MDrAO8A7wDsQAgADIALgAy -AFAAZQByAGYAaQBsACAAZwBlAG4A6QByAGkAYwBvACAAZABlACAAYwBpAG4AegBlAG4AdABvAHMAIABk -AGEAIABHAGEAbQBtAGEAIAAyACwAMgBBAGwAZwBlAG0AZQBlAG4AIABnAHIAaQBqAHMAIABnAGEAbQBt -AGEAIAAyACwAMgAtAHAAcgBvAGYAaQBlAGwAUABlAHIAZgBpAGwAIABnAGUAbgDpAHIAaQBjAG8AIABk -AGUAIABnAGEAbQBtAGEAIABkAGUAIABnAHIAaQBzAGUAcwAgADIALAAyDiMOMQ4HDioONQ5BDgEOIQ4h -DjIOQA4BDiMOIg5MDhcOMQ5IDicORA4bACAAMgAuADIARwBlAG4AZQBsACAARwByAGkAIABHAGEAbQBh -ACAAMgAsADIAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAG4AIABnAGEAbQBtAGEAIAAyACwAMgAg -AC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0AawBpACAARwByAGEAeQAgAEcAYQBtAG0AYQAg -ADIALgAyACAAcAByAG8AZgBpAGwAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABz -AHoAYQByAG8BWwBjAGkAIABnAGEAbQBtAGEAIAAyACwAMgY6BicGRQYnACAAMgAuADIAIAZEBkgGRgAg -BjEGRQYnBi8GSgAgBjkGJwZFBB4EMQRJBDAETwAgBEEENQRABDAETwAgBDMEMAQ8BDwEMAAgADIALAAy -AC0EPwRABD4ERAQ4BDsETABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAEcAYQBtAG0AYQAgADIALgAy -ACAAUAByAG8AZgBpAGwAZQAAdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxMgAAWFlaIAAA -AAAAAPNRAAEAAAABFsxjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBP -AFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADl -AOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGp -AbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2 -AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQT -BCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXF -BdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfS -B+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9 -ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0N -DSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBD -EGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPl -FAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3 -GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7 -HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1 -IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo -JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizX -LQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNG -M38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2 -OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGs -Qe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mp -SfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIx -UnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtF -W5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTp -ZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8e -b3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn -ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH -hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/ -kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3S -nkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sC -q3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR -uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dB -x7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV -1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN -5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt -9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//9IrLC0uWiRjbGFzc25hbWVYJGNsYXNzZXNf -EBBOU0JpdG1hcEltYWdlUmVwoy0vMFpOU0ltYWdlUmVwWE5TT2JqZWN00issMjNXTlNBcnJheaIyMNIr -LDU2Xk5TTXV0YWJsZUFycmF5ozUyMNM4OQ86OzxXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzS -Kyw+P1dOU0NvbG9yoj4w0issQUJXTlNJbWFnZaJBMAAIABEAGgAkACkAMgA3AEkATABRAFMAYgBoAHUA -fACLAJIAnwCmAK4AsACyALQAuQC7AL0AxADJANQA1gDYANoA3wDiAOQA5gDoAO8BBgEiASQBJhOWE5sT -phOvE8ITxhPRE9oT3xPnE+oT7xP+FAIUCRQRFB4UIxQlFCcULBQ0FDcUPBREAAAAAAAAAgEAAAAAAAAA -QwAAAAAAAAAAAAAAAAAAFEc - - - - -YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T -S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T -U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN -EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdMPJSYnKBRf -EBROU1RJRkZSZXByZXNlbnRhdGlvbl8QGU5TSW50ZXJuYWxMYXlvdXREaXJlY3Rpb26ACIAHTxESbE1N -ACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEG -AAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEW -AAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFT -AAMAAAACAAEAAYdzAAcAABGcAAAA0AAAAAAAABGcYXBwbAIAAABtbnRyR1JBWVhZWiAH3AAIABcADwAu -AA9hY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAHlkc2NtAAABPAAA -CBpjcHJ0AAAJWAAAACN3dHB0AAAJfAAAABRrVFJDAAAJkAAACAxkZXNjAAAAAAAAAB9HZW5lcmljIEdy -YXkgR2FtbWEgMi4yIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNr -U0sAAAAuAAABhGRhREsAAAA6AAABsmNhRVMAAAA4AAAB7HZpVk4AAABAAAACJHB0QlIAAABKAAACZHVr -VUEAAAAsAAACrmZyRlUAAAA+AAAC2mh1SFUAAAA0AAADGHpoVFcAAAAaAAADTGtvS1IAAAAiAAADZm5i -Tk8AAAA6AAADiGNzQ1oAAAAoAAADwmhlSUwAAAAkAAAD6nJvUk8AAAAqAAAEDmRlREUAAABOAAAEOGl0 -SVQAAABOAAAEhnN2U0UAAAA4AAAE1HpoQ04AAAAaAAAFDGphSlAAAAAmAAAFJmVsR1IAAAAqAAAFTHB0 -UE8AAABSAAAFdm5sTkwAAABAAAAFyGVzRVMAAABMAAAGCHRoVEgAAAAyAAAGVHRyVFIAAAAkAAAGhmZp -RkkAAABGAAAGqmhySFIAAAA+AAAG8HBsUEwAAABKAAAHLmFyRUcAAAAsAAAHeHJ1UlUAAAA6AAAHpGVu -VVMAAAA8AAAH3gBWAWEAZQBvAGIAZQBjAG4A4QAgAHMAaQB2AOEAIABnAGEAbQBhACAAMgAsADIARwBl -AG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQAtAHAAcgBvAGYAaQBsAEcAYQBt -AG0AYQAgAGQAZQAgAGcAcgBpAHMAbwBzACAAZwBlAG4A6AByAGkAYwBhACAAMgAuADIAQx6lAHUAIABo -AOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1AG4AZwAgAEcAYQBtAG0AYQAgADIALgAyAFAAZQBy -AGYAaQBsACAARwBlAG4A6QByAGkAYwBvACAAZABhACAARwBhAG0AYQAgAGQAZQAgAEMAaQBuAHoAYQBz -ACAAMgAsADIEFwQwBDMEMAQ7BEwEPQQwACAARwByAGEAeQAtBDMEMAQ8BDAAIAAyAC4AMgBQAHIAbwBm -AGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAZwByAGkAcwAgAGcAYQBtAG0AYQAgADIALAAyAMEAbAB0 -AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABnAGEAbQBtAGEAIAAyAC4AMpAadShwcJaOUUlepgAy -AC4AMoJyX2ljz4/wx3y8GAAg1ozAyQAgrBC5yAAgADIALgAyACDVBLhc0wzHfABHAGUAbgBlAHIAaQBz -AGsAIABnAHIA5QAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8AZgBpAGwATwBiAGUAYwBuAOEAIAFh -AGUAZADhACAAZwBhAG0AYQAgADIALgAyBdIF0AXeBdQAIAXQBeQF1QXoACAF2wXcBdwF2QAgADIALgAy -AEcAYQBtAGEAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMBAwAgADIALAAyAEEAbABsAGcAZQBtAGUAaQBu -AGUAcwAgAEcAcgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBmAGkAbAAgAEcAYQBtAG0AYQAgADIALAAy -AFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwAgAGQAZQBsAGwAYQAg -AGcAYQBtAG0AYQAgADIALAAyAEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAMgAsADIAIABnAGEAbQBt -AGEAcAByAG8AZgBpAGxmbpAacHBepnz7ZXAAMgAuADJjz4/wZYdO9k4AgiwwsDDsMKQwrDDzMN4AIAAy -AC4AMgAgMNcw7TDVMKEwpDDrA5MDtQO9A7kDugPMACADkwO6A8EDuQAgA5MDrAO8A7wDsQAgADIALgAy -AFAAZQByAGYAaQBsACAAZwBlAG4A6QByAGkAYwBvACAAZABlACAAYwBpAG4AegBlAG4AdABvAHMAIABk -AGEAIABHAGEAbQBtAGEAIAAyACwAMgBBAGwAZwBlAG0AZQBlAG4AIABnAHIAaQBqAHMAIABnAGEAbQBt -AGEAIAAyACwAMgAtAHAAcgBvAGYAaQBlAGwAUABlAHIAZgBpAGwAIABnAGUAbgDpAHIAaQBjAG8AIABk -AGUAIABnAGEAbQBtAGEAIABkAGUAIABnAHIAaQBzAGUAcwAgADIALAAyDiMOMQ4HDioONQ5BDgEOIQ4h -DjIOQA4BDiMOIg5MDhcOMQ5IDicORA4bACAAMgAuADIARwBlAG4AZQBsACAARwByAGkAIABHAGEAbQBh -ACAAMgAsADIAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAG4AIABnAGEAbQBtAGEAIAAyACwAMgAg -AC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0AawBpACAARwByAGEAeQAgAEcAYQBtAG0AYQAg -ADIALgAyACAAcAByAG8AZgBpAGwAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABz -AHoAYQByAG8BWwBjAGkAIABnAGEAbQBtAGEAIAAyACwAMgY6BicGRQYnACAAMgAuADIAIAZEBkgGRgAg -BjEGRQYnBi8GSgAgBjkGJwZFBB4EMQRJBDAETwAgBEEENQRABDAETwAgBDMEMAQ8BDwEMAAgADIALAAy -AC0EPwRABD4ERAQ4BDsETABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAEcAYQBtAG0AYQAgADIALgAy -ACAAUAByAG8AZgBpAGwAZQAAdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxMgAAWFlaIAAA -AAAAAPNRAAEAAAABFsxjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBP -AFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADl -AOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGp -AbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2 -AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQT -BCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXF -BdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfS -B+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9 -ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0N -DSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBD -EGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPl -FAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3 -GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7 -HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1 -IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo -JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizX -LQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNG -M38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2 -OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGs -Qe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mp -SfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIx -UnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtF -W5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTp -ZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8e -b3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn -ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH -hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/ -kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3S -nkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sC -q3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR -uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dB -x7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV -1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN -5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt -9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//9IrLC0uWiRjbGFzc25hbWVYJGNsYXNzZXNf -EBBOU0JpdG1hcEltYWdlUmVwoy0vMFpOU0ltYWdlUmVwWE5TT2JqZWN00issMjNXTlNBcnJheaIyMNIr -LDU2Xk5TTXV0YWJsZUFycmF5ozUyMNM4OQ86OzxXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzS -Kyw+P1dOU0NvbG9yoj4w0issQUJXTlNJbWFnZaJBMAAIABEAGgAkACkAMgA3AEkATABRAFMAYgBoAHUA -fACLAJIAnwCmAK4AsACyALQAuQC7AL0AxADJANQA1gDYANoA3wDiAOQA5gDoAO8BBgEiASQBJhOWE5sT -phOvE8ITxhPRE9oT3xPnE+oT7xP+FAIUCRQRFB4UIxQlFCcULBQ0FDcUPBREAAAAAAAAAgEAAAAAAAAA -QwAAAAAAAAAAAAAAAAAAFEc - - - - -YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T -S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T -U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN -EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdMPJSYnKBRf -EBROU1RJRkZSZXByZXNlbnRhdGlvbl8QGU5TSW50ZXJuYWxMYXlvdXREaXJlY3Rpb26ACIAHTxESbE1N -ACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEG -AAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEW -AAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFT -AAMAAAACAAEAAYdzAAcAABGcAAAA0AAAAAAAABGcYXBwbAIAAABtbnRyR1JBWVhZWiAH3AAIABcADwAu -AA9hY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAHlkc2NtAAABPAAA -CBpjcHJ0AAAJWAAAACN3dHB0AAAJfAAAABRrVFJDAAAJkAAACAxkZXNjAAAAAAAAAB9HZW5lcmljIEdy -YXkgR2FtbWEgMi4yIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNr -U0sAAAAuAAABhGRhREsAAAA6AAABsmNhRVMAAAA4AAAB7HZpVk4AAABAAAACJHB0QlIAAABKAAACZHVr -VUEAAAAsAAACrmZyRlUAAAA+AAAC2mh1SFUAAAA0AAADGHpoVFcAAAAaAAADTGtvS1IAAAAiAAADZm5i -Tk8AAAA6AAADiGNzQ1oAAAAoAAADwmhlSUwAAAAkAAAD6nJvUk8AAAAqAAAEDmRlREUAAABOAAAEOGl0 -SVQAAABOAAAEhnN2U0UAAAA4AAAE1HpoQ04AAAAaAAAFDGphSlAAAAAmAAAFJmVsR1IAAAAqAAAFTHB0 -UE8AAABSAAAFdm5sTkwAAABAAAAFyGVzRVMAAABMAAAGCHRoVEgAAAAyAAAGVHRyVFIAAAAkAAAGhmZp -RkkAAABGAAAGqmhySFIAAAA+AAAG8HBsUEwAAABKAAAHLmFyRUcAAAAsAAAHeHJ1UlUAAAA6AAAHpGVu -VVMAAAA8AAAH3gBWAWEAZQBvAGIAZQBjAG4A4QAgAHMAaQB2AOEAIABnAGEAbQBhACAAMgAsADIARwBl -AG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQAtAHAAcgBvAGYAaQBsAEcAYQBt -AG0AYQAgAGQAZQAgAGcAcgBpAHMAbwBzACAAZwBlAG4A6AByAGkAYwBhACAAMgAuADIAQx6lAHUAIABo -AOwAbgBoACAATQDgAHUAIAB4AOEAbQAgAEMAaAB1AG4AZwAgAEcAYQBtAG0AYQAgADIALgAyAFAAZQBy -AGYAaQBsACAARwBlAG4A6QByAGkAYwBvACAAZABhACAARwBhAG0AYQAgAGQAZQAgAEMAaQBuAHoAYQBz -ACAAMgAsADIEFwQwBDMEMAQ7BEwEPQQwACAARwByAGEAeQAtBDMEMAQ8BDAAIAAyAC4AMgBQAHIAbwBm -AGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAZwByAGkAcwAgAGcAYQBtAG0AYQAgADIALAAyAMEAbAB0 -AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABnAGEAbQBtAGEAIAAyAC4AMpAadShwcJaOUUlepgAy -AC4AMoJyX2ljz4/wx3y8GAAg1ozAyQAgrBC5yAAgADIALgAyACDVBLhc0wzHfABHAGUAbgBlAHIAaQBz -AGsAIABnAHIA5QAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8AZgBpAGwATwBiAGUAYwBuAOEAIAFh -AGUAZADhACAAZwBhAG0AYQAgADIALgAyBdIF0AXeBdQAIAXQBeQF1QXoACAF2wXcBdwF2QAgADIALgAy -AEcAYQBtAGEAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMBAwAgADIALAAyAEEAbABsAGcAZQBtAGUAaQBu -AGUAcwAgAEcAcgBhAHUAcwB0AHUAZgBlAG4ALQBQAHIAbwBmAGkAbAAgAEcAYQBtAG0AYQAgADIALAAy -AFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwAgAGQAZQBsAGwAYQAg -AGcAYQBtAG0AYQAgADIALAAyAEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAMgAsADIAIABnAGEAbQBt -AGEAcAByAG8AZgBpAGxmbpAacHBepnz7ZXAAMgAuADJjz4/wZYdO9k4AgiwwsDDsMKQwrDDzMN4AIAAy -AC4AMgAgMNcw7TDVMKEwpDDrA5MDtQO9A7kDugPMACADkwO6A8EDuQAgA5MDrAO8A7wDsQAgADIALgAy -AFAAZQByAGYAaQBsACAAZwBlAG4A6QByAGkAYwBvACAAZABlACAAYwBpAG4AegBlAG4AdABvAHMAIABk -AGEAIABHAGEAbQBtAGEAIAAyACwAMgBBAGwAZwBlAG0AZQBlAG4AIABnAHIAaQBqAHMAIABnAGEAbQBt -AGEAIAAyACwAMgAtAHAAcgBvAGYAaQBlAGwAUABlAHIAZgBpAGwAIABnAGUAbgDpAHIAaQBjAG8AIABk -AGUAIABnAGEAbQBtAGEAIABkAGUAIABnAHIAaQBzAGUAcwAgADIALAAyDiMOMQ4HDioONQ5BDgEOIQ4h -DjIOQA4BDiMOIg5MDhcOMQ5IDicORA4bACAAMgAuADIARwBlAG4AZQBsACAARwByAGkAIABHAGEAbQBh -ACAAMgAsADIAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAG4AIABnAGEAbQBtAGEAIAAyACwAMgAg -AC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0AawBpACAARwByAGEAeQAgAEcAYQBtAG0AYQAg -ADIALgAyACAAcAByAG8AZgBpAGwAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8AZgBpAGwAIABz -AHoAYQByAG8BWwBjAGkAIABnAGEAbQBtAGEAIAAyACwAMgY6BicGRQYnACAAMgAuADIAIAZEBkgGRgAg -BjEGRQYnBi8GSgAgBjkGJwZFBB4EMQRJBDAETwAgBEEENQRABDAETwAgBDMEMAQ8BDwEMAAgADIALAAy -AC0EPwRABD4ERAQ4BDsETABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAEcAYQBtAG0AYQAgADIALgAy -ACAAUAByAG8AZgBpAGwAZQAAdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxMgAAWFlaIAAA -AAAAAPNRAAEAAAABFsxjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBP -AFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADl -AOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGp -AbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2 -AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQT -BCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXF -BdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfS -B+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9 -ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0N -DSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBD -EGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPl -FAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3 -GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7 -HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1 -IaEhziH7IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo -JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizX -LQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNG -M38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2 -OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGs -Qe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mp -SfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIx -UnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtF -W5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTp -ZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8e -b3hv0XArcIZw4HE6cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnn -ekZ6pXsEe2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH -hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/ -kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3S -nkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sC -q3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjR -uUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dB -x7/IPci8yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV -1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN -5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt -9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//9IrLC0uWiRjbGFzc25hbWVYJGNsYXNzZXNf -EBBOU0JpdG1hcEltYWdlUmVwoy0vMFpOU0ltYWdlUmVwWE5TT2JqZWN00issMjNXTlNBcnJheaIyMNIr -LDU2Xk5TTXV0YWJsZUFycmF5ozUyMNM4OQ86OzxXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzS -Kyw+P1dOU0NvbG9yoj4w0issQUJXTlNJbWFnZaJBMAAIABEAGgAkACkAMgA3AEkATABRAFMAYgBoAHUA -fACLAJIAnwCmAK4AsACyALQAuQC7AL0AxADJANQA1gDYANoA3wDiAOQA5gDoAO8BBgEiASQBJhOWE5sT -phOvE8ITxhPRE9oT3xPnE+oT7xP+FAIUCRQRFB4UIxQlFCcULBQ0FDcUPBREAAAAAAAAAgEAAAAAAAAA -QwAAAAAAAAAAAAAAAAAAFEc - - + diff --git a/Block Management/AllowlistScraper.m b/Block Management/AllowlistScraper.m index ebc408f3..de35de52 100644 --- a/Block Management/AllowlistScraper.m +++ b/Block Management/AllowlistScraper.m @@ -30,6 +30,29 @@ @implementation AllowlistScraper return nil; } + // these sites are often featured in "share links" on a wide variety of websites + // we really don't want to add them to the allowlist, since they're also + // super distracting. So explicitly flag them to skip in this process + NSArray* neverAddSites = @[ + @"instagram.com", + @"www.instagram.com", + @"twitter.com", + @"www.twitter.com", + @"facebook.com", + @"www.facebook.com", + @"reddit.com", + @"www.reddit.com", + @"youtube.com", + @"www.youtube.com", + @"pinterest.com", + @"plus.google.com", + @"www.pinterest.com", + @"linkedin.com", + @"www.linkedin.com", + @"tumblr.com", + @"www.tumblr.com" + ]; + NSDataDetector* dataDetector = [[NSDataDetector alloc] initWithTypes: NSTextCheckingTypeLink error: nil]; NSCountedSet* relatedEntries = [NSCountedSet set]; [dataDetector enumerateMatchesInString: html @@ -38,7 +61,8 @@ @implementation AllowlistScraper usingBlock:^(NSTextCheckingResult* result, NSMatchingFlags flags, BOOL* stop) { if (([result.URL.scheme isEqualToString: @"http"] || [result.URL.scheme isEqualToString: @"https"]) && [result.URL.host length] - && ![result.URL.host isEqualToString: rootURL.host]) { + && ![result.URL.host isEqualToString: rootURL.host] + && ![neverAddSites containsObject: result.URL.host]) { [relatedEntries addObject: [SCBlockEntry entryWithHostname: result.URL.host]]; } }]; diff --git a/Block Management/BlockManager.h b/Block Management/BlockManager.h index 10e2c878..3aa31d2d 100644 --- a/Block Management/BlockManager.h +++ b/Block Management/BlockManager.h @@ -22,15 +22,15 @@ #import #import "PacketFilter.h" -#import "HostFileBlocker.h" #import "NSString+IPAddress.h" @class SCBlockEntry; +@class HostFileBlockerSet; @interface BlockManager : NSObject { NSOperationQueue* opQueue; PacketFilter* pf; - HostFileBlocker* hostsBlocker; + HostFileBlockerSet* hostBlockerSet; BOOL hostsBlockingEnabled; BOOL isAllowlist; BOOL allowLocal; diff --git a/Block Management/BlockManager.m b/Block Management/BlockManager.m index 045ab853..d7989f51 100644 --- a/Block Management/BlockManager.m +++ b/Block Management/BlockManager.m @@ -25,6 +25,7 @@ #import "SCBlockEntry.h" #include #include +#import "HostFileBlockerSet.h" @implementation BlockManager @@ -51,7 +52,7 @@ - (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeC [opQueue setMaxConcurrentOperationCount: 35]; pf = [[PacketFilter alloc] initAsAllowlist: allowlist]; - hostsBlocker = [[HostFileBlocker alloc] init]; + hostBlockerSet = [[HostFileBlockerSet alloc] init]; hostsBlockingEnabled = NO; isAllowlist = allowlist; @@ -65,13 +66,16 @@ - (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeC } - (void)prepareToAddBlock { - if([hostsBlocker containsSelfControlBlock]) { - [hostsBlocker removeSelfControlBlock]; - [hostsBlocker writeNewFileContents]; - } + for (HostFileBlocker* blocker in hostBlockerSet.blockers) { + if([blocker containsSelfControlBlock]) { + [blocker removeSelfControlBlock]; + [blocker writeNewFileContents]; + } + } - if(!isAllowlist && ![hostsBlocker containsSelfControlBlock] && [hostsBlocker createBackupHostsFile]) { - [hostsBlocker addSelfControlBlockHeader]; + if(!isAllowlist && ![hostBlockerSet.defaultBlocker containsSelfControlBlock]) { + [hostBlockerSet createBackupHostsFile]; + [hostBlockerSet addSelfControlBlockHeader]; hostsBlockingEnabled = YES; } else { hostsBlockingEnabled = NO; @@ -83,7 +87,7 @@ - (void)enterAppendMode { NSLog(@"ERROR: can't append to allowlist block"); return; } - if(![hostsBlocker containsSelfControlBlock]) { + if(![hostBlockerSet.defaultBlocker containsSelfControlBlock]) { NSLog(@"ERROR: can't append to hosts block that doesn't yet exist"); return; } @@ -100,7 +104,7 @@ - (void)finishAppending { NSTimeInterval runTime = [finishedRunning timeIntervalSinceDate: startedRunning]; NSLog(@"BlockManager: Operation queue ran in %f seconds!", runTime); - [hostsBlocker writeNewFileContents]; + [hostBlockerSet writeNewFileContents]; [pf finishAppending]; [pf refreshPFRules]; appendMode = NO; @@ -115,8 +119,8 @@ - (void)finalizeBlock { NSLog(@"BlockManager: Operation queue ran in %f seconds!", runTime); if(hostsBlockingEnabled) { - [hostsBlocker addSelfControlBlockFooter]; - [hostsBlocker writeNewFileContents]; + [hostBlockerSet addSelfControlBlockFooter]; + [hostBlockerSet writeNewFileContents]; } [pf startBlock]; @@ -149,41 +153,51 @@ - (void)addBlockEntry:(SCBlockEntry*)entry { [pf addRuleWithIP: nil port: entry.port maskLen: 0]; } else if(isIPv4) { // current we do NOT do ipfw blocking for IPv6 [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 = [BlockManager ipAddressesForDomainName: entry.hostname]; + } else if(!isIP) { // domain name + // Google requires special handling + if ([self domainIsGoogle: entry.hostname]) { + if (isAllowlist) { + // just add the whole Google IP range, it's way too error-prone to do an allowlist block of Google any other way + // last updated: 9/23/21 from https://www.gstatic.com/ipranges/goog.json + [self addGoogleIPsToPF]; + } + // for blocklist blocks, just skip blocking Google by IP + // because we'd end up blocking more than the user wants (i.e. Search/Mail) + // rely on the domain-level blocking instead + } else { + // non-Google domains just get looked up and blocked by IP + NSArray* addresses = [BlockManager ipAddressesForDomainName: entry.hostname]; - for(NSUInteger i = 0; i < [addresses count]; i++) { - NSString* ip = addresses[i]; + for(NSUInteger i = 0; i < [addresses count]; i++) { + NSString* ip = addresses[i]; - [pf addRuleWithIP: ip port: entry.port maskLen: entry.maskLen]; - } + [pf addRuleWithIP: ip port: entry.port maskLen: entry.maskLen]; + } + } } if(hostsBlockingEnabled && ![entry.hostname isEqualToString: @"*"] && !entry.port && !isIP) { if (appendMode) { - [hostsBlocker appendExistingBlockWithRuleForDomain: entry.hostname]; + [hostBlockerSet appendExistingBlockWithRuleForDomain: entry.hostname]; } else { - [hostsBlocker addRuleBlockingDomain: entry.hostname]; + [hostBlockerSet addRuleBlockingDomain: entry.hostname]; } } } - (void)addBlockEntryFromString:(NSString*)entryString { - NSLog(@"adding block entry from string: %@", entryString); SCBlockEntry* entry = [SCBlockEntry entryFromString: entryString]; // nil means that we don't have anything valid to block in this entry if (entry == nil) return; - [self addBlockEntry: entry]; - + // enqueue new entries _before_ running this one, so they can happen in parallel NSArray* relatedEntries = [self relatedBlockEntriesForEntry: entry]; - NSLog(@"Enqueuing related entries to %@: %@", entry, relatedEntries); for (SCBlockEntry* relatedEntry in relatedEntries) { [self enqueueBlockEntry: relatedEntry]; } + + [self addBlockEntry: entry]; } - (void)addBlockEntriesFromStrings:(NSArray*)blockList { @@ -199,12 +213,12 @@ - (BOOL)clearBlock { [pf stopBlock: false]; BOOL pfSuccess = ![pf containsSelfControlBlock]; - [hostsBlocker removeSelfControlBlock]; - BOOL hostSuccess = [hostsBlocker writeNewFileContents]; + [hostBlockerSet removeSelfControlBlock]; + BOOL hostSuccess = [hostBlockerSet 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). - [hostsBlocker revertFileContentsToDisk]; - hostSuccess = hostSuccess && ![hostsBlocker containsSelfControlBlock]; + [hostBlockerSet revertFileContentsToDisk]; + hostSuccess = hostSuccess && ![hostBlockerSet containsSelfControlBlock]; BOOL clearedSuccessfully = hostSuccess && pfSuccess; @@ -217,12 +231,12 @@ - (BOOL)clearBlock { } if (!hostSuccess) { NSLog(@"WARNING: Error removing hostfile block. Attempting to restore host file backup."); - [hostsBlocker restoreBackupHostsFile]; + [hostBlockerSet restoreBackupHostsFile]; } clearedSuccessfully = ![self blockIsActive]; - if ([hostsBlocker containsSelfControlBlock]) { + if ([hostBlockerSet.defaultBlocker containsSelfControlBlock]) { NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block."); } if ([pf containsSelfControlBlock]) { @@ -233,7 +247,7 @@ - (BOOL)clearBlock { } } - [hostsBlocker deleteBackupHostsFile]; + [hostBlockerSet deleteBackupHostsFile]; return clearedSuccessfully; } @@ -242,12 +256,12 @@ - (BOOL)forceClearBlock { [pf stopBlock: YES]; BOOL pfSuccess = ![pf containsSelfControlBlock]; - [hostsBlocker removeSelfControlBlock]; - BOOL hostSuccess = [hostsBlocker writeNewFileContents]; + [hostBlockerSet removeSelfControlBlock]; + BOOL hostSuccess = [hostBlockerSet 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). - [hostsBlocker revertFileContentsToDisk]; - hostSuccess = hostSuccess && ![hostsBlocker containsSelfControlBlock]; + [hostBlockerSet revertFileContentsToDisk]; + hostSuccess = hostSuccess && ![hostBlockerSet containsSelfControlBlock]; BOOL clearedSuccessfully = hostSuccess && pfSuccess; @@ -259,12 +273,12 @@ - (BOOL)forceClearBlock { } if (!hostSuccess) { NSLog(@"WARNING: Error removing hostfile block. Attempting to restore host file backup."); - [hostsBlocker restoreBackupHostsFile]; + [hostBlockerSet restoreBackupHostsFile]; } clearedSuccessfully = ![self blockIsActive]; - if ([hostsBlocker containsSelfControlBlock]) { + if ([hostBlockerSet.defaultBlocker containsSelfControlBlock]) { NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block."); } if (clearedSuccessfully) { @@ -276,7 +290,7 @@ - (BOOL)forceClearBlock { } - (BOOL)blockIsActive { - return [hostsBlocker containsSelfControlBlock] || [pf containsSelfControlBlock]; + return [hostBlockerSet.defaultBlocker containsSelfControlBlock] || [pf containsSelfControlBlock]; } - (NSArray*)commonSubdomainsForHostName:(NSString*)hostName { @@ -315,6 +329,12 @@ - (NSArray*)commonSubdomainsForHostName:(NSString*)hostName { [newHosts addObject: @"api.twitter.com"]; } + if ([hostName hasSuffix: @"netflix.com"]) { + [newHosts addObject: @"assets.nflxext.com"]; + [newHosts addObject: @"codex.nflxext.com"]; + [newHosts addObject: @"nflxext.com"]; + } + // Block the domain with no subdomains, if www.domain is blocked if([hostName rangeOfString: @"www."].location == 0) { [newHosts addObject: [hostName substringFromIndex: 4]]; @@ -429,4 +449,83 @@ - (BOOL)domainIsGoogle:(NSString*)domainName { return relatedEntries; } +- (void)addGoogleIPsToPF { + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.34.208.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"8.35.192.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"23.236.48.0" port: 0 maskLen: 240]; + [pf addRuleWithIP: @"23.251.128.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"34.64.0.0" port: 0 maskLen: 10]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"34.128.0.0" port: 0 maskLen: 10]; + [pf addRuleWithIP: @"35.184.0.0" port: 0 maskLen: 13]; + [pf addRuleWithIP: @"35.192.0.0" port: 0 maskLen: 14]; + [pf addRuleWithIP: @"35.196.0.0" port: 0 maskLen: 15]; + [pf addRuleWithIP: @"35.198.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"35.199.0.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"35.199.128.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"35.200.0.0" port: 0 maskLen: 13]; + [pf addRuleWithIP: @"35.208.0.0" port: 0 maskLen: 12]; + [pf addRuleWithIP: @"35.224.0.0" port: 0 maskLen: 12]; + [pf addRuleWithIP: @"35.240.0.0" port: 0 maskLen: 13]; + [pf addRuleWithIP: @"64.15.112.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"64.233.160.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"66.102.0.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"66.249.64.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"70.32.128.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"72.14.192.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"74.114.24.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"74.125.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"104.154.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"104.196.0.0" port: 0 maskLen: 14]; + [pf addRuleWithIP: @"104.237.160.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"107.167.160.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"107.178.192.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"108.59.80.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"108.170.192.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"108.177.0.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"130.211.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"136.112.0.0" port: 0 maskLen: 12]; + [pf addRuleWithIP: @"142.250.0.0" port: 0 maskLen: 15]; + [pf addRuleWithIP: @"146.148.0.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"162.216.148.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"162.222.176.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"172.110.32.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"172.217.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"172.253.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"173.194.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"173.255.112.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"192.158.28.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"192.178.0.0" port: 0 maskLen: 15]; + [pf addRuleWithIP: @"193.186.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"199.36.154.0" port: 0 maskLen: 23]; + [pf addRuleWithIP: @"199.36.156.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"199.192.112.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"199.223.232.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"207.223.160.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"208.65.152.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"208.68.108.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"208.81.188.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"208.117.224.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"209.85.128.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"216.58.192.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"216.73.80.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"216.239.32.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"2001:4860::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2404:6800::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2404:f340::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2600:1900::" port: 0 maskLen: 28]; + [pf addRuleWithIP: @"2606:73c0::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2607:f8b0::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2620:11a:a000::" port: 0 maskLen: 40]; + [pf addRuleWithIP: @"2620:120:e000::" port: 0 maskLen: 40]; + [pf addRuleWithIP: @"2800:3f0::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2a00:1450::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2c0f:fb50::" port: 0 maskLen: 32]; +} + @end diff --git a/Block Management/HostFileBlocker.h b/Block Management/HostFileBlocker.h index 7f2788ec..eb47b3eb 100755 --- a/Block Management/HostFileBlocker.h +++ b/Block Management/HostFileBlocker.h @@ -22,15 +22,7 @@ #import - -@interface HostFileBlocker : NSObject { - NSLock* strLock; - NSMutableString* newFileContents; - NSStringEncoding stringEnc; - NSFileManager* fileMan; -} - -+ (BOOL)blockFoundInHostsFile; +@protocol HostFileBlocker - (BOOL)deleteBackupHostsFile; @@ -54,3 +46,19 @@ - (void)removeSelfControlBlock; @end + + +@interface HostFileBlocker : NSObject { + NSString* hostFilePath; + + NSLock* strLock; + NSMutableString* newFileContents; + NSStringEncoding stringEnc; + NSFileManager* fileMan; +} + +- (instancetype)initWithPath:(NSString*)path; + ++ (BOOL)blockFoundInHostsFile; + +@end diff --git a/Block Management/HostFileBlocker.m b/Block Management/HostFileBlocker.m index 0c8b2aec..d96f71a7 100755 --- a/Block Management/HostFileBlocker.m +++ b/Block Management/HostFileBlocker.m @@ -38,11 +38,15 @@ @implementation HostFileBlocker -- (HostFileBlocker*)init { +- (instancetype)init { + return [self initWithPath: kHostFileBlockerPath]; +} +- (instancetype)initWithPath:(NSString*)path { if(self = [super init]) { + hostFilePath = path; fileMan = [[NSFileManager alloc] init]; strLock = [[NSLock alloc] init]; - newFileContents = [NSMutableString stringWithContentsOfFile: kHostFileBlockerPath usedEncoding: &stringEnc error: NULL]; + newFileContents = [NSMutableString stringWithContentsOfFile: hostFilePath usedEncoding: &stringEnc error: NULL]; if(!newFileContents) { // if we lost our hosts file, replace it with the OS X default newFileContents = [NSMutableString stringWithString: kDefaultHostsFileContents]; @@ -65,7 +69,7 @@ + (BOOL)blockFoundInHostsFile { - (void)revertFileContentsToDisk { [strLock lock]; - newFileContents = [NSMutableString stringWithContentsOfFile: kHostFileBlockerPath usedEncoding: &stringEnc error: NULL]; + newFileContents = [NSMutableString stringWithContentsOfFile: hostFilePath usedEncoding: &stringEnc error: NULL]; if(!newFileContents) { newFileContents = [NSMutableString stringWithString: kDefaultHostsFileContents]; } @@ -76,37 +80,43 @@ - (void)revertFileContentsToDisk { - (BOOL)writeNewFileContents { [strLock lock]; - BOOL ret = [newFileContents writeToFile: kHostFileBlockerPath atomically: YES encoding: stringEnc error: NULL]; + BOOL ret = [newFileContents writeToFile: hostFilePath atomically: YES encoding: stringEnc error: NULL]; [strLock unlock]; return ret; } +- (NSString*)backupHostFilePath { + return [hostFilePath stringByAppendingPathExtension: @"bak"]; +} + - (BOOL)createBackupHostsFile { [self deleteBackupHostsFile]; - if (![fileMan fileExistsAtPath: @"/etc/hosts"]) { - [kDefaultHostsFileContents writeToFile: @"/etc/hosts" atomically:true encoding: NSUTF8StringEncoding error: NULL]; + if (![fileMan fileExistsAtPath: hostFilePath]) { + [kDefaultHostsFileContents writeToFile: hostFilePath atomically:true encoding: NSUTF8StringEncoding error: NULL]; } - if(![fileMan isReadableFileAtPath: @"/etc/hosts"] || [fileMan fileExistsAtPath: @"/etc/hosts.bak"]) { + if(![fileMan isReadableFileAtPath: hostFilePath] || [fileMan fileExistsAtPath: [self backupHostFilePath]]) { return NO; } - return [fileMan copyItemAtPath: @"/etc/hosts" toPath: @"/etc/hosts.bak" error: nil]; + return [fileMan copyItemAtPath: hostFilePath toPath: [self backupHostFilePath] error: nil]; } - (BOOL)deleteBackupHostsFile { - if(![fileMan isDeletableFileAtPath: @"/etc/hosts.bak"]) + if(![fileMan isDeletableFileAtPath: [self backupHostFilePath]]) return NO; - return [fileMan removeItemAtPath: @"/etc/hosts.bak" error: nil]; + return [fileMan removeItemAtPath: [self backupHostFilePath] error: nil]; } - (BOOL)restoreBackupHostsFile { - if(![fileMan removeItemAtPath: @"/etc/hosts" error: nil]) + NSString* backupPath = [self backupHostFilePath]; + + if(![fileMan removeItemAtPath: hostFilePath error: nil]) return NO; - if(![fileMan isReadableFileAtPath: @"/etc/hosts.bak"] || ![fileMan moveItemAtPath: @"/etc/hosts.bak" toPath: @"/etc/hosts" error: nil]) + if(![fileMan isReadableFileAtPath: backupPath] || ![fileMan moveItemAtPath: backupPath toPath: hostFilePath error: nil]) return NO; return YES; @@ -182,15 +192,36 @@ - (void)removeSelfControlBlock { NSRange endRange = [newFileContents rangeOfString: kHostFileBlockerSelfControlFooter]; // generate a delete range that properly removes the block from the hosts file - // the -1 and +1 are to remove the newlines before/after the header/footer - // the MAX/MIN prevent us from trying to delete beyond the file boundaries - NSUInteger deleteRangeStart = MAX(0, startRange.location - 1); + NSUInteger deleteRangeStart = startRange.location; NSUInteger deleteRangeLength; + + // there are usually newlines placed before/after the header/footer + // we should remove them if possible to keep the hosts file looking tidy + // only remove the previous character if we aren't at the start of the file (or we'll crash) + if (deleteRangeStart > 0) { + unichar prevChar = [newFileContents characterAtIndex: deleteRangeStart - 1]; + // if the previous character isn't a newline, don't delete it + if ([[NSCharacterSet newlineCharacterSet] characterIsMember: prevChar]) { + deleteRangeStart--; + } + } + + NSUInteger maxDeleteLength = [newFileContents length] - deleteRangeStart; // if we lost the block footer somehow... well, crap, just delete everything below the header + // this isn't ideal and we might bork other stuff, but it's better than leaving the block on if (endRange.location == NSNotFound) { - deleteRangeLength = [newFileContents length] - deleteRangeStart; + deleteRangeLength = maxDeleteLength; } else { - deleteRangeLength = MIN([newFileContents length] - deleteRangeStart, (endRange.location + endRange.length) - deleteRangeStart + 1); + deleteRangeLength = MIN(maxDeleteLength, (endRange.location + endRange.length) - deleteRangeStart); + + // as above, look at removing the excess newline if possible + if (deleteRangeLength < maxDeleteLength) { + unichar nextChar = [newFileContents characterAtIndex: deleteRangeStart + deleteRangeLength]; + // if the next character isn't a newline, don't delete it + if ([[NSCharacterSet newlineCharacterSet] characterIsMember: nextChar]) { + deleteRangeLength++; + } + } } NSRange deleteRange = NSMakeRange(deleteRangeStart, deleteRangeLength); diff --git a/Block Management/HostFileBlockerSet.h b/Block Management/HostFileBlockerSet.h new file mode 100644 index 00000000..249022d6 --- /dev/null +++ b/Block Management/HostFileBlockerSet.h @@ -0,0 +1,20 @@ +// +// HostFileBlockerSet.h +// SelfControl +// +// Created by Charlie Stigler on 3/7/21. +// + +#import +#import "HostFileBlocker.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface HostFileBlockerSet : NSObject + +@property (readonly) NSArray* blockers; +@property (readonly) HostFileBlocker* defaultBlocker; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Block Management/HostFileBlockerSet.m b/Block Management/HostFileBlockerSet.m new file mode 100644 index 00000000..0cc61b2e --- /dev/null +++ b/Block Management/HostFileBlockerSet.m @@ -0,0 +1,121 @@ +// +// HostFileBlockerSet.m +// SelfControl +// +// Created by Charlie Stigler on 3/7/21. +// + +#import "HostFileBlockerSet.h" + +@implementation HostFileBlockerSet + +- (instancetype)init { + return [self initWithCommonFiles]; +} +- (instancetype)initWithCommonFiles { + NSFileManager* fileMan = [NSFileManager defaultManager]; + NSArray* commonBackupHostFilePaths = @[ + // Juniper Pulse + @"/etc/pulse-hosts.bak", + @"/etc/jnpr-pulse-hosts.bak", + @"/etc/pulse.hosts.bak", + @"/etc/jnpr-nc-hosts.bak", + + // Cisco AnyConnect + @"/etc/hosts.ac" + ]; + + NSMutableArray* hostFileBlockers = [NSMutableArray arrayWithCapacity: commonBackupHostFilePaths.count + 1]; + + _defaultBlocker = [HostFileBlocker new]; + [hostFileBlockers addObject: _defaultBlocker]; + + for (NSString* path in commonBackupHostFilePaths) { + if ([fileMan isReadableFileAtPath: path]) { + NSLog(@"INFO: found backup VPN host file at %@", path); + HostFileBlocker* blocker = [[HostFileBlocker alloc] initWithPath: path]; + [hostFileBlockers addObject: blocker]; + } + } + + _blockers = hostFileBlockers; + + return self; +} + +- (BOOL)deleteBackupHostsFile { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker deleteBackupHostsFile]; + } + return ret; +} + +- (void)revertFileContentsToDisk { + for (HostFileBlocker* blocker in self.blockers) { + [blocker revertFileContentsToDisk]; + } +} + +- (BOOL)writeNewFileContents { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker writeNewFileContents]; + } + return ret; +} + +- (void)addSelfControlBlockHeader { + for (HostFileBlocker* blocker in self.blockers) { + [blocker addSelfControlBlockHeader]; + } +} + +- (void)addSelfControlBlockFooter { + for (HostFileBlocker* blocker in self.blockers) { + [blocker addSelfControlBlockFooter]; + } +} + +- (BOOL)createBackupHostsFile { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker createBackupHostsFile]; + } + return ret; +} + +- (BOOL)restoreBackupHostsFile { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker restoreBackupHostsFile]; + } + return ret; +} + +- (void)addRuleBlockingDomain:(NSString*)domainName { + for (HostFileBlocker* blocker in self.blockers) { + [blocker addRuleBlockingDomain: domainName]; + } +} +- (void)appendExistingBlockWithRuleForDomain:(NSString*)domainName { + for (HostFileBlocker* blocker in self.blockers) { + [blocker appendExistingBlockWithRuleForDomain: domainName]; + } +} + +- (BOOL)containsSelfControlBlock { + BOOL ret = NO; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret || [blocker containsSelfControlBlock]; + } + return ret; +} + +- (void)removeSelfControlBlock { + for (HostFileBlocker* blocker in self.blockers) { + [blocker removeSelfControlBlock]; + } +} + +@end diff --git a/Block Management/HostImporter.m b/Block Management/HostImporter.m index 478f7f64..43b2475d 100755 --- a/Block Management/HostImporter.m +++ b/Block Management/HostImporter.m @@ -42,6 +42,7 @@ + (NSArray*)commonDistractingWebsites { @"vine.co", @"pinterest.com", @"stumbleupon.com", + @"instagram.com", ]; } + (NSArray*)newsAndPublications { diff --git a/Common/SCFileWatcher.h b/Common/SCFileWatcher.h new file mode 100644 index 00000000..4ef12241 --- /dev/null +++ b/Common/SCFileWatcher.h @@ -0,0 +1,27 @@ +// +// SCFileWatcher.h +// SelfControl +// +// Created by Charlie Stigler on 3/20/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^SCFileWatcherCallback)(NSError* _Nullable error); + +@interface SCFileWatcher : NSObject + +@property (readonly) FSEventStreamRef eventStream; +@property (strong, readonly) SCFileWatcherCallback callbackBlock; +@property (strong, readonly) NSString* filePath; + ++ (instancetype)watcherWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock; +- (instancetype)initWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock; + +- (void)stopWatching; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Common/SCFileWatcher.m b/Common/SCFileWatcher.m new file mode 100644 index 00000000..4a26448e --- /dev/null +++ b/Common/SCFileWatcher.m @@ -0,0 +1,104 @@ +// +// SCFileWatcher.m +// SelfControl +// +// Created by Charlie Stigler on 3/20/21. +// + +#import "SCFileWatcher.h" +#include + +@implementation SCFileWatcher + +static void SCFileWatcherGlobalCallback( + ConstFSEventStreamRef streamRef, + void *callbackCtxInfo, + size_t numEvents, + void *eventPaths, // CFArrayRef + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + NSArray* paths = (__bridge NSArray*)eventPaths; + SCFileWatcher* watcher = (__bridge SCFileWatcher*)callbackCtxInfo; + [watcher directoryWatcherTriggered: paths flags: eventFlags]; +} + +- (void)directoryWatcherTriggered:(NSArray*)eventPaths flags:(const FSEventStreamEventFlags[])eventFlags { + BOOL triggerFileWatcher = NO; + + for (unsigned int i = 0; i < eventPaths.count; i++) { + NSString* eventPath = [eventPaths[i] stringByStandardizingPath]; + + if ([eventPath isEqualToString: self.filePath]) { + triggerFileWatcher = YES; + } + } + + if (triggerFileWatcher) { + self.callbackBlock(nil); + } +} + ++ (instancetype)watcherWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock { + return [[SCFileWatcher new] initWithFile: watchPath block: callbackBlock]; +} + +- (instancetype)initWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock { + self = [super init]; + + NSFileManager* fileMan = [NSFileManager defaultManager]; + _filePath = [watchPath stringByStandardizingPath]; + BOOL isDirectory; + [fileMan fileExistsAtPath: self.filePath isDirectory: &isDirectory]; + + NSString* directoryPath; + if (isDirectory) { + directoryPath = self.filePath; + } else { + directoryPath = [self.filePath stringByDeletingLastPathComponent]; + } + + FSEventStreamContext callbackCtx; + callbackCtx.version = 0; + callbackCtx.info = (__bridge void *)self; + callbackCtx.retain = NULL; + callbackCtx.release = NULL; + callbackCtx.copyDescription = NULL; + + FSEventStreamRef eventStream = FSEventStreamCreate( + kCFAllocatorDefault, + &SCFileWatcherGlobalCallback, + &callbackCtx, // context + (__bridge CFArrayRef)@[directoryPath], + kFSEventStreamEventIdSinceNow, + 1.5, // seconds to throttle callbacks + kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagMarkSelf | kFSEventStreamCreateFlagIgnoreSelf | kFSEventStreamCreateFlagFileEvents + ); + + FSEventStreamScheduleWithRunLoop(eventStream, + [[NSRunLoop currentRunLoop] getCFRunLoop], + kCFRunLoopDefaultMode); + if (!FSEventStreamStart(eventStream)) { + NSLog(@"WARNING: failed to start watching file %@", watchPath); + FSEventStreamUnscheduleFromRunLoop(eventStream, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopDefaultMode); + FSEventStreamInvalidate(eventStream); + FSEventStreamRelease(eventStream); + return nil; + } + + _eventStream = eventStream; + _callbackBlock = callbackBlock; + + return self; +} + +- (void)stopWatching { + FSEventStreamStop(self.eventStream); + FSEventStreamUnscheduleFromRunLoop(self.eventStream, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopDefaultMode); + FSEventStreamInvalidate(self.eventStream); + FSEventStreamRelease(self.eventStream); + + _eventStream = NULL; +} + +@end diff --git a/Common/SCSentry.m b/Common/SCSentry.m index 58717d2b..5b97b797 100644 --- a/Common/SCSentry.m +++ b/Common/SCSentry.m @@ -19,7 +19,6 @@ + (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"; @@ -104,13 +103,14 @@ + (void)updateDefaultsContext { 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) + // (because unnecessary, and Sentry dies if you feed it 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"]; + [defaultsDict removeObjectForKey: @"SULastProfileSubmissionDate"]; #ifndef TESTING [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { diff --git a/Common/SCSettings.m b/Common/SCSettings.m index 21d62545..e0b35082 100644 --- a/Common/SCSettings.m +++ b/Common/SCSettings.m @@ -247,6 +247,19 @@ - (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))comple error: &createDirectoryErr]; if (!createDirectorySuccessful) { NSLog(@"WARNING: Failed to create %@ folder to store SCSettings. Error was %@", SETTINGS_FILE_DIR, createDirectoryErr); + [SCSentry addBreadcrumb: [NSString stringWithFormat: @"Failed to create directory for SCSettings with error %@", createDirectoryErr] category:@"settings"]; + } + + NSError* chmodDirectoryErr; + BOOL chmodDirectorySuccessful = [[NSFileManager defaultManager] + setAttributes: @{ + NSFilePosixPermissions: [NSNumber numberWithShort: 0755] + } + ofItemAtPath: SETTINGS_FILE_DIR + error: &chmodDirectoryErr]; + if (!chmodDirectorySuccessful) { + NSLog(@"WARNING: Failed to set permissions on %@ folder to store SCSettings. Error was %@", SETTINGS_FILE_DIR, chmodDirectoryErr); + [SCSentry addBreadcrumb: [NSString stringWithFormat: @"Failed to set directory permissions for SCSettings with error %@", chmodDirectoryErr] category:@"settings"]; } NSError* writeErr; diff --git a/Common/SCXPCAuthorization.m b/Common/SCXPCAuthorization.m index a749a800..f4b6c141 100644 --- a/Common/SCXPCAuthorization.m +++ b/Common/SCXPCAuthorization.m @@ -80,7 +80,10 @@ + (NSDictionary *)commandInfo @"group": @"admin", @"timeout": @(120), // 2 minutes @"shared": @(YES), - @"version": @1 // not entirely sure what this does TBH + @"version": @1, // not entirely sure what this does TBH + // Enable Touch ID and other biometric authentication on macOS 10.12.2+ + // The authenticate mechanism supports Touch ID when available + @"mechanisms": @[@"builtin:authenticate"] }; } diff --git a/Common/SCXPCClient.m b/Common/SCXPCClient.m index 89b8dfed..5436c73e 100644 --- a/Common/SCXPCClient.m +++ b/Common/SCXPCClient.m @@ -167,7 +167,6 @@ - (void)installDaemon:(void(^)(NSError*))callback { ); if(status) { - NSLog(@"copied rights with status %d", 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) { @@ -183,8 +182,21 @@ - (void)installDaemon:(void(^)(NSError*))callback { return; } - + CFErrorRef cfError; + + // in some cases, SMJobBless will fail if we don't first remove the currently running daemon + // it's not clear why exactly or what the exact cause is, but I can reproduce consistently + // by running a 100-site whitelist block, then immediately trying to start another block + // I consistently get the error (CFErrorDomainLaunchd error 2) + SILENCE_OSX10_10_DEPRECATION( + SMJobRemove(kSMDomainSystemLaunchd, CFSTR("org.eyebeam.selfcontrold"), self->_authRef, YES, &cfError); + ); + if (cfError) { + NSLog(@"WARNING: Failed to remove existing selfcontrold daemon with error %@", cfError); + cfError = NULL; + } + BOOL result = (BOOL)SMJobBless( kSMDomainSystemLaunchd, CFSTR("org.eyebeam.selfcontrold"), diff --git a/Common/Utility/SCMigrationUtilities.h b/Common/Utility/SCMigrationUtilities.h index 44ee95d6..f650348e 100644 --- a/Common/Utility/SCMigrationUtilities.h +++ b/Common/Utility/SCMigrationUtilities.h @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)copyLegacySettingsToDefaults; + (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID; ++ (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID ignoreRunningBlock:(BOOL)ignoreRunningBlock; @end diff --git a/Common/Utility/SCMigrationUtilities.m b/Common/Utility/SCMigrationUtilities.m index f5d162dc..b9b280e5 100644 --- a/Common/Utility/SCMigrationUtilities.m +++ b/Common/Utility/SCMigrationUtilities.m @@ -210,7 +210,7 @@ + (void)copyLegacySettingsToDefaults { // - 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 { ++ (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID ignoreRunningBlock:(BOOL)ignoreRunningBlock { NSLog(@"Clearing legacy settings!"); BOOL runningAsRoot = (geteuid() == 0); @@ -224,7 +224,7 @@ + (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID { } // if we're gonna clear settings, there can't be a block running anywhere. otherwise, we should wait! - if ([SCBlockUtilities legacyBlockIsRunning]) { + if ([SCBlockUtilities legacyBlockIsRunning] && !ignoreRunningBlock) { NSLog(@"ERROR: Can't clear legacy settings because a block is ongoing!"); NSError* err = [SCErr errorWithCode: 702]; [SCSentry captureError: err]; @@ -290,5 +290,10 @@ + (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID { return retErr; } ++ (NSError*)clearLegacySettingsForUser:(uid_t)controllingUID { + return [SCMigrationUtilities clearLegacySettingsForUser: controllingUID ignoreRunningBlock: NO]; +} + + @end diff --git a/Common/Utility/SCMiscUtilities.h b/Common/Utility/SCMiscUtilities.h index 6bd00e2d..0e357d3b 100644 --- a/Common/Utility/SCMiscUtilities.h +++ b/Common/Utility/SCMiscUtilities.h @@ -20,7 +20,9 @@ + (BOOL)systemThirdPartyCrashReportingEnabled; -+ (NSArray*) cleanBlocklistEntry:(NSString*)rawEntry; ++ (NSArray*)cleanBlocklistEntry:(NSString*)rawEntry; + ++ (NSArray*)cleanBlocklist:(NSArray*)blocklist; + (NSDictionary*) defaultsDictForUser:(uid_t)controllingUID; @@ -28,5 +30,6 @@ + (BOOL)errorIsAuthCanceled:(NSError*)err; ++ (NSString*)killerKeyForDate:(NSDate*)date; @end diff --git a/Common/Utility/SCMiscUtilities.m b/Common/Utility/SCMiscUtilities.m index 8018ef35..a1b2ea74 100644 --- a/Common/Utility/SCMiscUtilities.m +++ b/Common/Utility/SCMiscUtilities.m @@ -170,6 +170,22 @@ + (BOOL)systemThirdPartyCrashReportingEnabled { return @[[NSString stringWithFormat: @"%@%@%@", str, maskString, portString]]; } ++ (NSArray*)cleanBlocklist:(NSArray*)blocklist { + NSMutableArray* cleanedList = [NSMutableArray arrayWithCapacity: blocklist.count]; + + // for now, we just remove whitespace and then remove empty entries + // in the future, this method could do more thorough cleaning + for (NSString* blockString in blocklist) { + NSString* cleanedString = [blockString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if (cleanedString.length > 0) { + [cleanedList addObject: cleanedString]; + } + } + + return cleanedList; +} + + (NSDictionary*) defaultsDictForUser:(uid_t) controllingUID { if (geteuid() != 0) { // if we're not root, we can't just get defaults for some arbitrary user @@ -227,4 +243,8 @@ + (BOOL)errorIsAuthCanceled:(NSError*)err { return homeDirectoryURLs; } ++ (NSString*)killerKeyForDate:(NSDate*)date { + return [SCMiscUtilities sha1: [NSString stringWithFormat: @"SelfControlKillerKey%@%@", [SCMiscUtilities getSerialNumber], [date descriptionWithLocale: nil]]]; +} + @end diff --git a/Daemon/SCDaemon.m b/Daemon/SCDaemon.m index d6f118cb..3fd746a2 100644 --- a/Daemon/SCDaemon.m +++ b/Daemon/SCDaemon.m @@ -9,6 +9,7 @@ #import "SCDaemonProtocol.h" #import "SCDaemonXPC.h" #import"SCDaemonBlockMethods.h" +#import "SCFileWatcher.h" static NSString* serviceName = @"org.eyebeam.selfcontrold"; float const INACTIVITY_LIMIT_SECS = 60 * 2; // 2 minutes @@ -27,6 +28,8 @@ @interface SCDaemon () @property (strong, readwrite) NSTimer* inactivityTimer; @property (nonatomic, strong, readwrite) NSDate* lastActivityDate; +@property (nonatomic, strong) SCFileWatcher* hostsFileWatcher; + @end @implementation SCDaemon @@ -63,6 +66,13 @@ - (void)start { [self startInactivityTimer]; [self resetInactivityTimer]; + + self.hostsFileWatcher = [SCFileWatcher watcherWithFile: @"/etc/hosts" block:^(NSError * _Nonnull error) { + if ([SCBlockUtilities anyBlockIsRunning]) { + NSLog(@"INFO: hosts file changed, checking block integrity"); + [SCDaemonBlockMethods checkBlockIntegrity]; + } + }]; } - (void)startCheckupTimer { @@ -80,7 +90,7 @@ - (void)startCheckupTimer { } self.checkupTimer = [NSTimer scheduledTimerWithTimeInterval: 1 repeats: YES block:^(NSTimer * _Nonnull timer) { - [SCDaemonBlockMethods checkupBlock]; + [SCDaemonBlockMethods checkupBlock]; }]; // run the first checkup immediately! @@ -127,6 +137,10 @@ - (void)dealloc { [self.inactivityTimer invalidate]; self.inactivityTimer = nil; } + if (self.hostsFileWatcher) { + [self.hostsFileWatcher stopWatching]; + self.hostsFileWatcher = nil; + } } #pragma mark - NSXPCListenerDelegate @@ -145,9 +159,7 @@ - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConne 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); + SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\") and info [CFBundleVersion] >= \"407\" 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] = EG6ZYP3AQH)"), kSecCSDefaultFlags, &isSelfControlApp); OSStatus clientValidityStatus = SecCodeCheckValidity(guest, kSecCSDefaultFlags, isSelfControlApp); CFRelease(guest); diff --git a/Daemon/SCDaemonBlockMethods.h b/Daemon/SCDaemonBlockMethods.h index 72bfede8..ca1ccf6d 100644 --- a/Daemon/SCDaemonBlockMethods.h +++ b/Daemon/SCDaemonBlockMethods.h @@ -29,6 +29,8 @@ NS_ASSUME_NONNULL_BEGIN // (i.e. extends the block) + (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; ++ (void)checkBlockIntegrity; + @end NS_ASSUME_NONNULL_END diff --git a/Daemon/SCDaemonBlockMethods.m b/Daemon/SCDaemonBlockMethods.m index 85943456..1a6cf0a5 100644 --- a/Daemon/SCDaemonBlockMethods.m +++ b/Daemon/SCDaemonBlockMethods.m @@ -12,6 +12,7 @@ #import "BlockManager.h" #import "SCDaemon.h" #import "LaunchctlHelper.h" +#import "HostFileBlockerSet.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 @@ -278,13 +279,13 @@ + (void)checkupBlock { [SCSentry addBreadcrumb: @"Daemon method checkupBlock called" category: @"daemon"]; - SCSettings* settings = [SCSettings sharedSettings]; - NSTimeInterval integrityCheckIntervalSecs = 10.0; + NSTimeInterval integrityCheckIntervalSecs = 15.0; static NSDate* lastBlockIntegrityCheck; if (lastBlockIntegrityCheck == nil) { lastBlockIntegrityCheck = [NSDate distantPast]; } + BOOL shouldRunIntegrityCheck = NO; 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! @@ -313,9 +314,9 @@ + (void)checkupBlock { 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 @@ -325,44 +326,63 @@ + (void)checkupBlock { // 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."); + shouldRunIntegrityCheck = YES; } [[SCDaemon sharedDaemon] resetInactivityTimer]; [self.daemonMethodLock unlock]; + + // if we need to run an integrity check, we need to do it at the very end after we give up our lock + // because checkBlockIntegrity requests its own lock, and we don't want it to deadlock + if (shouldRunIntegrityCheck) { + [SCDaemonBlockMethods checkBlockIntegrity]; + } +} + ++ (void)checkBlockIntegrity { + if (![SCDaemonBlockMethods lockOrTimeout: nil timeout: CHECKUP_LOCK_TIMEOUT]) { + return; + } + + [SCSentry addBreadcrumb: @"Daemon method checkBlockIntegrity called" category: @"daemon"]; + + SCSettings* settings = [SCSettings sharedSettings]; + PacketFilter* pf = [[PacketFilter alloc] init]; + HostFileBlockerSet* hostFileBlockerSet = [[HostFileBlockerSet alloc] init]; + if(![pf containsSelfControlBlock] || (![settings boolForKey: @"ActiveBlockAsWhitelist"] && ![hostFileBlockerSet.defaultBlocker 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]; + + [hostFileBlockerSet removeSelfControlBlock]; + BOOL success = [hostFileBlockerSet 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). + [hostFileBlockerSet revertFileContentsToDisk]; + if(!success || [hostFileBlockerSet.defaultBlocker containsSelfControlBlock]) { + NSLog(@"WARNING: Error removing host file block. Attempting to restore backup."); + + if([hostFileBlockerSet 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. + [hostFileBlockerSet 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: Integrity check ran; readded block rules."); + } else NSLog(@"INFO: Integrity check ran; no action needed."); + + [self.daemonMethodLock unlock]; } @end diff --git a/Daemon/selfcontrold-Info.plist b/Daemon/selfcontrold-Info.plist index d0e33b5b..fc0522a2 100755 --- a/Daemon/selfcontrold-Info.plist +++ b/Daemon/selfcontrold-Info.plist @@ -38,9 +38,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - $(MARKETING_VERSION) + 4.0.2 CFBundleVersion - 403 + 410 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSAppTransportSecurity @@ -52,7 +52,7 @@ 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) + 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] = EG6ZYP3AQH) diff --git a/DomainListWindowController.h b/DomainListWindowController.h index 1ab48225..b7d43564 100755 --- a/DomainListWindowController.h +++ b/DomainListWindowController.h @@ -39,6 +39,8 @@ @property (getter=isReadOnly) BOOL readOnly; +- (void)refreshDomainList; + // Called when the add button is clicked. Adds a new empty string to the domain // list, reloads the table view, and highlights and opens that cell for editing. - (IBAction)addDomain:(id)sender; diff --git a/DomainListWindowController.m b/DomainListWindowController.m index a1798d4e..aadb88f2 100755 --- a/DomainListWindowController.m +++ b/DomainListWindowController.m @@ -48,6 +48,20 @@ - (void)awakeFromNib { [self updateWindowTitle]; } +- (void)refreshDomainList { + // end any current editing to trigger saving blocklist + if (![NSThread isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self refreshDomainList]; + }); + return; + } + + [[self window] makeFirstResponder: self]; + domainList_ = [[defaults_ arrayForKey: @"Blocklist"] mutableCopy]; + [domainListTableView_ reloadData]; +} + - (void)showWindow:(id)sender { [[self window] makeKeyAndOrderFront: self]; @@ -113,8 +127,11 @@ - (void)controlTextDidEndEditing:(NSNotification *)note { NSInteger editedRow = [domainListTableView_ editedRow]; NSString* editedString = [[[[note userInfo] objectForKey: @"NSFieldEditor"] textStorage] string]; editedString = [editedString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - if (editedRow >= 0 && ![editedString length]) { + + // sometimes we get an edited row index that's out-of-bounds for weird reasons, + // e.g. if we're editing an empty row and then start a block, the data will get reloaded + // and the row will not exist by the time this method gets called. We can ignore in that case + if (editedRow >= 0 && editedRow < domainListTableView_.numberOfRows && !editedString.length) { NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex: (NSUInteger)editedRow]; [domainListTableView_ beginUpdates]; [domainListTableView_ removeRowsAtIndexes: indexSet withAnimation: NSTableViewAnimationSlideUp]; @@ -260,7 +277,7 @@ - (void)showAllowlistWarning { 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.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. This can cause unexpected behavior. 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]; diff --git a/FirstTime.rtf b/FirstTime.rtf index 7a23506e..5e2b5874 100644 --- a/FirstTime.rtf +++ b/FirstTime.rtf @@ -21,7 +21,7 @@ \pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\sl312\slmult1\pardirnatural \ls1\ilvl0 \fs24 \cf0 {\listtext 1. } -\b Click "Edit Blacklist" +\b Click "Edit Blocklist" \b0 and add the domain names of your most distracting websites -- for example, "facebook.com". Or use the Import function to add a pre-made list of websites.\ {\listtext 2. } \b Move the slider diff --git a/Info.plist b/Info.plist index 0751d527..85a72114 100755 --- a/Info.plist +++ b/Info.plist @@ -38,9 +38,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.0 alpha 2 + 4.0.2 CFBundleVersion - 403 + 410 LSApplicationCategoryType public.app-category.productivity LSHasLocalizedDisplayName @@ -58,7 +58,7 @@ 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) + 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] = EG6ZYP3AQH) SUEnableAutomaticChecks diff --git a/Podfile b/Podfile index 5ba2fe47..1d715879 100644 --- a/Podfile +++ b/Podfile @@ -14,24 +14,29 @@ target "SelfControl" do pod 'TransformerKit', '~> 1.1.1' pod 'FormatterKit/TimeIntervalFormatter', '~> 1.8.0' pod 'LetsMove', '~> 1.24' - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' + + # Add test target + target 'SelfControlTests' do + inherit! :complete + end end target "SelfControl Killer" do use_frameworks! :linkage => :static - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' 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' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' end target "selfcontrol-cli" do - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' end target "org.eyebeam.selfcontrold" do - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '6.1.3' + pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' end post_install do |pi| diff --git a/README.md b/README.md index 9de723bd..0e55eae6 100755 --- a/README.md +++ b/README.md @@ -1,28 +1,32 @@ -[SelfControl](http://selfcontrolapp.com) -=========== +# [SelfControl][website] -About ------ -SelfControl is a free and open-source application for macOS that lets you block your own access to distracting websites, your mail servers, or anything else on the Internet. Just set a period of time to block for, add sites to your blacklist, and click "Start." Until that timer expires, you will be unable to access those sites—even if you restart your computer or delete the application. +

+ +

+ +## About + +SelfControl is a free and open-source application for macOS that lets you block **your own** access to distracting websites, your mail servers, or anything else on the Internet. Just set a period of time to block for, add sites to your blocklist, and click "Start Block". Until that timer expires, you will be unable to access those sites—even if you restart your computer or delete the application. + +## Credits -Credits -------- Developed by [Charlie Stigler](http://charliestigler.com), [Steve Lambert](http://visitsteve.com), and [others](https://github.com/SelfControlApp/selfcontrol/graphs/contributors). Your contributions very welcome! SelfControl is now available in 12 languages thanks to [the fine translators credited here](https://github.com/SelfControlApp/selfcontrol/wiki/Translation-Credits). -License -------- -SelfControl is free software under the GPL. See [this file](https://github.com/SelfControlApp/selfcontrol/blob/master/COPYING) for more details. +## License -Building For Development --------------------- +SelfControl is free software under the GPL. See [this file](./COPYING) for more details. -Users should always download the latest version of SelfControl from [our website]. If you want to contribute to SelfControl, you'll need to learn to build it for development. This can only be done on a Mac running a modern version of macOS. +## Building For Development + +Users should always download the latest version of SelfControl from [our website][website]. If you want to contribute to SelfControl, you'll need to learn to build it for development. This can only be done on a Mac running a modern version of macOS. 1. Clone the SelfControl repo from GitHub. 2. Make sure you have a recent version of Xcode and the Xcode command-line tools installed. 3. Install [CocoaPods](https://cocoapods.org/): `sudo gem install cocoapods` 4. Install the SelfControl dependencies using CocoaPods: `pod install` -5. Open the `selfcontrol.xcworkspace` file (NOT `selfcontrol.xcodeproj`) +5. Open the `selfcontrol.xcworkspace` file (**NOT** `selfcontrol.xcodeproj`) 6. Build and run (you may need to update/remove code signing settings to make it build properly) + +[website]: https://selfcontrolapp.com/ diff --git a/SCConstants.m b/SCConstants.m index 2b7a4f4d..80def483 100644 --- a/SCConstants.m +++ b/SCConstants.m @@ -49,7 +49,6 @@ @implementation SCConstants @"BadgeApplicationIcon": @YES, @"BlockDuration": @1, @"MaxBlockLength": @1440, - @"BlockLengthInterval": @15, @"WhitelistAlertSuppress": @NO, @"GetStartedShown": @NO, @"EvaluateCommonSubdomains": @YES, @@ -62,6 +61,9 @@ @implementation SCConstants // otherwise it defaults off, but we'll still prompt to ask them if we can send data @"EnableErrorReporting": @([SCMiscUtilities systemThirdPartyCrashReportingEnabled]), @"ErrorReportingPromptDismissed": @NO, + @"SuppressLongBlockWarning": @NO, + @"SuppressRestartFirefoxWarning": @NO, + @"FirstBlockStarted": @NO, @"V4MigrationComplete": @NO }; diff --git a/SCDurationSlider.h b/SCDurationSlider.h new file mode 100644 index 00000000..4dc34a00 --- /dev/null +++ b/SCDurationSlider.h @@ -0,0 +1,27 @@ +// +// SCTimeSlider.h +// SelfControl +// +// Created by Charlie Stigler on 4/17/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCDurationSlider : NSSlider + +@property (nonatomic, assign) NSInteger maxDuration; +@property (readonly) NSInteger durationValueMinutes; +@property (readonly) NSString* durationDescription; + +- (NSInteger)durationValueMinutes; +- (void)bindDurationToObject:(id)obj keyPath:(NSString*)keyPath; +- (NSString*)durationDescription; + ++ (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSeconds; ++ (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SCDurationSlider.m b/SCDurationSlider.m new file mode 100644 index 00000000..25e75376 --- /dev/null +++ b/SCDurationSlider.m @@ -0,0 +1,112 @@ +// +// SCTimeSlider.m +// SelfControl +// +// Created by Charlie Stigler on 4/17/21. +// + +#import "SCDurationSlider.h" +#import "SCTimeIntervalFormatter.h" +#import + +#define kValueTransformerName @"BlockDurationSliderTransformer" + +@implementation SCDurationSlider + +- (void)drawRect:(NSRect)dirtyRect { + [super drawRect:dirtyRect]; + + // Drawing code here. +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + if (self = [super initWithCoder: coder]) { + [self initializeDurationProperties]; + } + return self; +} +- (instancetype)init { + if (self = [super init]) { + [self initializeDurationProperties]; + } + return self; +} + +- (void)initializeDurationProperties { + // default: 1 day max + _maxDuration = 1440; + + // register an NSValueTransformer + [self registerMinutesValueTransformer]; +} + +- (void)setMaxDuration:(NSInteger)maxDuration { + _maxDuration = maxDuration; + [self setMinValue: 1]; // never start a block shorter than 1 minute + [self setMaxValue: self.maxDuration]; +} + +- (void)registerMinutesValueTransformer { + [NSValueTransformer registerValueTransformerWithName: kValueTransformerName + transformedValueClass: [NSNumber class] + returningTransformedValueWithBlock:^id _Nonnull(id _Nonnull value) { + // if it's not a number or convertable to one, IDK man + if (![value respondsToSelector: @selector(floatValue)]) return @0; + + long minutesValue = lroundf([value floatValue]); + return @(minutesValue); + }]; +} + +- (NSInteger)durationValueMinutes { + return lroundf(self.floatValue); +} + +- (void)bindDurationToObject:(id)obj keyPath:(NSString*)keyPath { + [self bind: @"value" + toObject: obj + withKeyPath: keyPath + options: @{ + NSContinuouslyUpdatesValueBindingOption: @YES, + NSValueTransformerNameBindingOption: kValueTransformerName + }]; +} + +- (NSString*)durationDescription { + return [SCDurationSlider timeSliderDisplayStringFromNumberOfMinutes: self.durationValueMinutes]; +} + +// String conversion utility methods + ++ (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSeconds { + static SCTimeIntervalFormatter* formatter = nil; + if (formatter == nil) { + formatter = [[SCTimeIntervalFormatter alloc] init]; + } + + NSString* formatted = [formatter stringForObjectValue:@(numberOfSeconds)]; + return formatted; +} + ++ (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes { + if (numberOfMinutes < 0) return @"Invalid duration"; + + static NSCalendar* gregorian = nil; + if (gregorian == nil) { + gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + } + + NSRange secondsRangePerMinute = [gregorian + rangeOfUnit:NSCalendarUnitSecond + inUnit:NSCalendarUnitMinute + forDate:[NSDate date]]; + NSInteger numberOfSecondsPerMinute = (NSInteger)NSMaxRange(secondsRangePerMinute); + + NSTimeInterval numberOfSecondsSelected = (NSTimeInterval)(numberOfSecondsPerMinute * numberOfMinutes); + + NSString* displayString = [SCDurationSlider timeSliderDisplayStringFromTimeInterval:numberOfSecondsSelected]; + return displayString; +} + + +@end diff --git a/SCError.strings b/SCError.strings index 48fcc012..7491c893 100644 --- a/SCError.strings +++ b/SCError.strings @@ -36,6 +36,10 @@ // 400-499 = errors generated in the killer "400" = "SelfControl couldn't manually clear the block, because there was an error running the helper tool."; +"401" = "Something went wrong, and SelfControl couldn't manually clear the block. Try again in a minute. If that doesn't work, go to www.selfcontrolapp.com/support to get help."; +"402" = "The killer helper tool couldn't launch because it had insufficient privileges."; +"403" = "The killer helper tool couldn't launch because it was provided the incorrect key."; +"404" = "The killer helper tool couldn't launch because it was provided an invalid key date."; // 500-599 = errors generated in the XPC client "500" = "There was an error trying to install SelfControl's helper tool: %@"; diff --git a/SCKillerHelper/main.m b/SCKillerHelper/main.m index 67516d23..18eb5fdc 100644 --- a/SCKillerHelper/main.m +++ b/SCKillerHelper/main.m @@ -12,33 +12,57 @@ #import "BlockManager.h" #import "SCSettings.h" #import "SCHelperToolUtilities.h" +#import "SCMiscUtilities.h" #import #import "SCMigrationUtilities.h" #import +#import "SCSentry.h" #define LOG_FILE @"~/Documents/SelfControl-Killer.log" int main(int argc, char* argv[]) { @autoreleasepool { + [SCSentry startSentry: @"com.selfcontrolapp.SCKillerHelper"]; + // 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."); + [SCSentry captureError: [SCErr errorWithCode: 402]]; exit(EXIT_FAILURE); } - if(argv[1] == NULL) { + if(argv[1] == NULL || argv[2] == NULL) { NSLog(@"ERROR: Not enough arguments"); exit(EXIT_FAILURE); } + + NSString* killerKey = @(argv[1]); + NSDate* keyDate = [[NSISO8601DateFormatter new] dateFromString: @(argv[2])]; + NSTimeInterval keyDateToNow = [[NSDate date] timeIntervalSinceDate: keyDate]; + + // key date must exist, not be in the future, and be in the past 10 seconds + if (keyDate == nil || keyDateToNow < 0 || keyDateToNow > 10) { + NSLog(@"ERROR: Key date invalid"); + [SCSentry captureError: [SCErr errorWithCode: 404]]; + exit(EX_USAGE); + } + + // keys must match + NSString* correctKey = [SCMiscUtilities killerKeyForDate: keyDate]; + if (![correctKey isEqualToString: killerKey]) { + NSLog(@"ERROR: Incorrect key"); + [SCSentry captureError: [SCErr errorWithCode: 403]]; + exit(EX_USAGE); + } - uid_t controllingUID = (uid_t)[@(argv[1]) intValue]; + uid_t controllingUID = (uid_t)[@(argv[3]) intValue]; if (controllingUID <= 0) { controllingUID = getuid(); } - + // 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); @@ -177,7 +201,7 @@ int main(int argc, char* argv[]) { if ([SCMigrationUtilities legacySettingsFoundForUser: controllingUID]) { [SCMigrationUtilities copyLegacySettingsToDefaults: controllingUID]; - [SCMigrationUtilities clearLegacySettingsForUser: controllingUID]; + [SCMigrationUtilities clearLegacySettingsForUser: controllingUID ignoreRunningBlock: YES]; [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"]; diff --git a/SCUIUtilities.h b/SCUIUtilities.h index ab0ad2ae..428dc3b8 100644 --- a/SCUIUtilities.h +++ b/SCUIUtilities.h @@ -22,9 +22,6 @@ NS_ASSUME_NONNULL_BEGIN + (BOOL)promptBrowserRestartIfNecessary; -+ (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSeconds; -+ (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes; - + (NSString*)blockTeaserStringWithMaxLength:(NSInteger)maxStringLen; // presents an error via a popup in the app diff --git a/SCUIUtilities.m b/SCUIUtilities.m index 156b634f..88fdfc31 100644 --- a/SCUIUtilities.m +++ b/SCUIUtilities.m @@ -7,14 +7,17 @@ #import "SCUIUtilities.h" #import -#import "SCTimeIntervalFormatter.h" @implementation SCUIUtilities + (NSString*)blockTeaserStringWithMaxLength:(NSInteger)maxStringLen { NSArray* blocklist; BOOL isAllowlist; - if ([SCUIUtilities blockIsRunning]) { + + // if we've got a block running (and it's from the modern system), + // the real source of truth is secured settings. + // if no block is running (or it's an old-school one), the best we've got is what's in defaults + if ([SCBlockUtilities modernBlockIsRunning]) { SCSettings* settings = [SCSettings sharedSettings]; blocklist = [settings valueForKey: @"ActiveBlocklist"]; isAllowlist = [settings boolForKey: @"ActiveBlockAsWhitelist"]; @@ -87,36 +90,6 @@ + (NSString*)blockTeaserStringWithMaxLength:(NSInteger)maxStringLen { return [startStr stringByAppendingString: siteStr]; } -+ (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSeconds { - static SCTimeIntervalFormatter* formatter = nil; - if (formatter == nil) { - formatter = [[SCTimeIntervalFormatter alloc] init]; - } - - NSString* formatted = [formatter stringForObjectValue:@(numberOfSeconds)]; - return formatted; -} - -+ (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes { - if (numberOfMinutes < 0) return @"Invalid duration"; - - static NSCalendar* gregorian = nil; - if (gregorian == nil) { - gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; - } - - NSRange secondsRangePerMinute = [gregorian - rangeOfUnit:NSCalendarUnitSecond - inUnit:NSCalendarUnitMinute - forDate:[NSDate date]]; - NSInteger numberOfSecondsPerMinute = (NSInteger)NSMaxRange(secondsRangePerMinute); - - NSTimeInterval numberOfSecondsSelected = (NSTimeInterval)(numberOfSecondsPerMinute * numberOfMinutes); - - NSString* displayString = [SCUIUtilities timeSliderDisplayStringFromTimeInterval:numberOfSecondsSelected]; - return displayString; -} - + (BOOL)networkConnectionIsAvailable { SCNetworkReachabilityFlags flags; @@ -144,14 +117,27 @@ + (BOOL)promptBrowserRestartIfNecessary { }); return retVal; } - + + NSString* RESTART_FF_SUPPRESSION_KEY = @"SuppressRestartFirefoxWarning"; + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + + // if they don't want the warnings, they don't get the warnings + if ([defaults boolForKey: RESTART_FF_SUPPRESSION_KEY]) { + return NO; + } + NSAlert* alert = [[NSAlert alloc] init]; [alert setMessageText: NSLocalizedString(@"Restart Firefox", "FireFox browser restart prompt")]; [alert setInformativeText:NSLocalizedString(@"SelfControl's block may not work properly in Firefox until you restart the browser. Do you want to quit Firefox now?", @"Message explaining Firefox restart requirement")]; [alert addButtonWithTitle: NSLocalizedString(@"Quit Firefox", @"Button to quit Firefox")]; [alert addButtonWithTitle: NSLocalizedString(@"Continue Without Restart", "Button to decline restarting Firefox")]; - + alert.showsSuppressionButton = YES; + NSModalResponse modalResponse = [alert runModal]; + if (alert.suppressionButton.state == NSControlStateValueOn) { + // no more warnings, they say + [defaults setBool: YES forKey: RESTART_FF_SUPPRESSION_KEY]; + } if (modalResponse == NSAlertFirstButtonReturn) { for (NSRunningApplication* ff in runningFF) { [ff terminate]; diff --git a/SelfControl Killer/AppDelegate.m b/SelfControl Killer/AppDelegate.m index 85df4799..dd6a0f87 100644 --- a/SelfControl Killer/AppDelegate.m +++ b/SelfControl Killer/AppDelegate.m @@ -8,6 +8,8 @@ #import "AppDelegate.h" #import "SCSettings.h" +#import "SCMiscUtilities.h" +#import "SCUIUtilities.h" @interface AppDelegate () @@ -19,6 +21,8 @@ @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [NSApplication sharedApplication].delegate = self; + [SCSentry startSentry: @"com.selfcontrolapp.SelfControl-Killer"]; + [self updateUserInterface]; } @@ -60,14 +64,19 @@ - (IBAction)killButtonClicked:(id)sender { char uidString[10]; snprintf(uidString, sizeof(uidString), "%d", getuid()); + + NSDate* keyDate = [NSDate date]; + NSString* killerKey = [SCMiscUtilities killerKeyForDate: keyDate]; + NSString* keyDateString = [[NSISO8601DateFormatter new] stringFromDate: keyDate]; + + char* args[] = { (char*)[killerKey UTF8String], (char*)[keyDateString UTF8String], uidString, NULL }; - char* args[] = { uidString, NULL }; - + FILE* pipe = NULL; status = AuthorizationExecuteWithPrivileges(authorizationRef, helperToolPath, kAuthorizationFlagDefaults, args, - NULL); + &pipe); if(status) { NSLog(@"WARNING: Authorized execution of helper tool returned failure status code %d", status); @@ -79,13 +88,33 @@ - (IBAction)killButtonClicked:(id)sender { } return; - } else { - 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."]; - [alert addButtonWithTitle: @"OK"]; - [alert runModal]; } + + // read until the pipe finishes so we wait for execution to end before we + // show the modal (so we can check if the block is cleared properly or not) + for (;;) { + ssize_t bytesRead = read(fileno(pipe), NULL, 256); + if (bytesRead < 1) break; + } + + // reload settings since they've probably just been messed with + [[SCSettings sharedSettings] reloadSettings]; + + // send some debug info to Sentry to help us track this issue + [SCSentry captureMessage: @"User manually cleared SelfControl block from the SelfControl Killer app"]; + + if ([SCBlockUtilities anyBlockIsRunning]) { + // ruh roh! the block wasn't cleared successfully, since it's still running + NSError* err = [SCErr errorWithCode: 401]; + [SCSentry captureError: err]; + [SCUIUtilities presentError: err]; + } else { + 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."]; + [alert addButtonWithTitle: @"OK"]; + [alert runModal]; + } [self.viewButton setEnabled: YES]; } diff --git a/SelfControl Killer/Info.plist b/SelfControl Killer/Info.plist index a41a5863..20ce6ff1 100644 --- a/SelfControl Killer/Info.plist +++ b/SelfControl Killer/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.0 alpha 2 + 4.0.2 CFBundleSignature ???? CFBundleVersion - 403 + 410 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/SelfControl.xcodeproj/project.pbxproj b/SelfControl.xcodeproj/project.pbxproj index bc06afef..2954aea5 100644 --- a/SelfControl.xcodeproj/project.pbxproj +++ b/SelfControl.xcodeproj/project.pbxproj @@ -7,13 +7,19 @@ 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 */; }; + 5E6BEEBB5C6E29DADDB344CF /* libPods-selfcontrol-cli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C85A094F15E20A0DB58665A8 /* libPods-selfcontrol-cli.a */; }; + 63BAC9E58A69B15D342B0E29 /* libPods-org.eyebeam.selfcontrold.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BF3973D41997900DF147B24 /* libPods-org.eyebeam.selfcontrold.a */; }; + 8CA8987104D2956493D6AF6B /* Pods_SelfControl_SelfControlTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F41DEF1E3926B4CF3AE2B76C /* Pods_SelfControl_SelfControlTests.framework */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; - 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 */; }; + CB066F6C2652037E0076964D /* HostFileBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB0AE290FA74566006229B3 /* HostFileBlocker.m */; }; + CB066F91265203800076964D /* HostFileBlockerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */; }; + CB066F92265203830076964D /* PacketFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCA91111960D87300AFD20C /* PacketFilter.m */; }; + CB066F93265203920076964D /* SCBlockEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */; }; + CB066F94265203970076964D /* SCErr.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1465B725B027E700130D2E /* SCErr.m */; }; + CB066F95265203990076964D /* SCSentry.m in Sources */ = {isa = PBXBuildFile; fileRef = CBADC27D25B22BC7000EE5BB /* SCSentry.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 */; }; @@ -50,6 +56,11 @@ 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 */; }; + CB5888DC25F60DC300B5C64D /* HostFileBlockerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */; }; + CB5888E225F60DC300B5C64D /* HostFileBlockerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */; }; + CB5888E325F60DC400B5C64D /* HostFileBlockerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */; }; + CB5888E425F60DC500B5C64D /* HostFileBlockerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */; }; + CB5888EA25F60DC500B5C64D /* HostFileBlockerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */; }; 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 */; }; @@ -120,6 +131,7 @@ CB9365620F8581B000EF284E /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CB9365610F8581B000EF284E /* dsa_pub.pem */; }; CB9366E80F85BEF100EF284E /* NSRemoveTemplate.jpg in Resources */ = {isa = PBXBuildFile; fileRef = CB9366E60F85BEF100EF284E /* NSRemoveTemplate.jpg */; }; CB9366E90F85BEF100EF284E /* NSAddTemplate.jpg in Resources */ = {isa = PBXBuildFile; fileRef = CB9366E70F85BEF100EF284E /* NSAddTemplate.jpg */; }; + CB953114262BC64F000C8309 /* SCDurationSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = CB953113262BC64F000C8309 /* SCDurationSlider.m */; }; CB9C80FC19CFB79700CDCAE1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9C80FB19CFB79700CDCAE1 /* main.m */; }; CB9C80FF19CFB79700CDCAE1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9C80FE19CFB79700CDCAE1 /* AppDelegate.m */; }; CB9C810419CFB79700CDCAE1 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB9C810219CFB79700CDCAE1 /* MainMenu.xib */; }; @@ -167,6 +179,13 @@ CBBF4E8B1582F8BD00E364D9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = CBBF4E891582F8BD00E364D9 /* InfoPlist.strings */; }; CBBF4E8E1582F8E000E364D9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CBBF4E8C1582F8E000E364D9 /* Localizable.strings */; }; CBBF4EE515830D7300E364D9 /* TimerWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBBF4EE715830D7300E364D9 /* TimerWindow.xib */; }; + CBC1F4B426070358008E3FA8 /* SCFileWatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = CBC1F4B226070358008E3FA8 /* SCFileWatcher.h */; }; + CBC1F4B526070358008E3FA8 /* SCFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */; }; + CBC1F4B626070358008E3FA8 /* SCFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */; }; + CBC1F4B726070358008E3FA8 /* SCFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */; }; + CBC1F4B826070358008E3FA8 /* SCFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */; }; + CBC1F4B926070358008E3FA8 /* SCFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */; }; + CBC1F4BA26070358008E3FA8 /* SCFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */; }; CBC2F8580F4672FE00CF2A42 /* LaunchctlHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC2F8570F4672FE00CF2A42 /* LaunchctlHelper.m */; }; CBCA91121960D87300AFD20C /* PacketFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCA91111960D87300AFD20C /* PacketFilter.m */; }; CBD2677011ED92DE00042CD8 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB9E90190F397FF6006DE6E4 /* CoreFoundation.framework */; }; @@ -174,6 +193,8 @@ 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 */; }; + CBDAB4D22651FDB000A1951C /* BlockManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CB25806116C1FDBE0059C99A /* BlockManager.m */; }; + CBDAB4F72651FDC900A1951C /* AllowlistScraper.m in Sources */ = {isa = PBXBuildFile; fileRef = CB73615F19E4FDA000E0924F /* AllowlistScraper.m */; }; CBDB118C2084239D0010397E /* FirstTime.xib in Resources */ = {isa = PBXBuildFile; fileRef = CBDB118E2084239D0010397E /* FirstTime.xib */; }; 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, ); }; }; @@ -185,8 +206,9 @@ 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 */; }; + D4EDD26C770910569C31D36F /* libPods-SCKillerHelper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF899A50A17F0C6C8C6B84A2 /* libPods-SCKillerHelper.a */; }; + DC4DBA9148D8D67A11899C5E /* Pods_SelfControl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBDE7B29D92764A409E4FDA /* Pods_SelfControl.framework */; }; + E263B809965135813A557CD5 /* Pods_SelfControl_Killer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86DAD6532C67CBE72E99084C /* Pods_SelfControl_Killer.framework */; }; F5B8CBEE19EE21C30026F3A5 /* SCTimeIntervalFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B8CBED19EE21C30026F3A5 /* SCTimeIntervalFormatter.m */; }; /* End PBXBuildFile section */ @@ -501,30 +523,31 @@ /* 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 = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 32B26CAAF2E2B648B3C0E892 /* 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 = ""; }; 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 = ""; }; - 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 = ""; }; + 50CA92A5EB2C31792CA00641 /* 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 = ""; }; + 5FA850E53EB64C54A1404AEF /* 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; }; + 64682B3371BA0A5044028058 /* 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 = ""; }; + 69102D5CD4E9672D0EFBF25B /* 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 = ""; }; + 6EBDE7B29D92764A409E4FDA /* Pods_SelfControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 77CFA479BA7C3ECBC15F96D7 /* 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 = ""; }; + 86DAD6532C67CBE72E99084C /* Pods_SelfControl_Killer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl_Killer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BF3973D41997900DF147B24 /* libPods-org.eyebeam.selfcontrold.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-org.eyebeam.selfcontrold.a"; 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 = ""; }; + AF899A50A17F0C6C8C6B84A2 /* libPods-SCKillerHelper.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SCKillerHelper.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C72025F2499F9F07E281CB50 /* Pods-SelfControl-SelfControlTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl-SelfControlTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests.release.xcconfig"; sourceTree = ""; }; + C85A094F15E20A0DB58665A8 /* libPods-selfcontrol-cli.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-selfcontrol-cli.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 /* SCUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCUtilityTests.m; sourceTree = ""; }; @@ -549,6 +572,8 @@ CB529BBD0F32B7ED00564FB8 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = ""; }; CB529BBE0F32B7ED00564FB8 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = ""; }; CB587E4F0F50FE8800C66A09 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = ""; }; + CB5888B125F6056400B5C64D /* HostFileBlockerSet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HostFileBlockerSet.h; sourceTree = ""; }; + CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HostFileBlockerSet.m; 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 = ""; }; @@ -592,6 +617,8 @@ CB9365610F8581B000EF284E /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = ""; }; CB9366E60F85BEF100EF284E /* NSRemoveTemplate.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = NSRemoveTemplate.jpg; sourceTree = ""; }; CB9366E70F85BEF100EF284E /* NSAddTemplate.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = NSAddTemplate.jpg; sourceTree = ""; }; + CB953112262BC64F000C8309 /* SCDurationSlider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCDurationSlider.h; sourceTree = ""; }; + CB953113262BC64F000C8309 /* SCDurationSlider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCDurationSlider.m; sourceTree = ""; }; CB9C80F119CFAAC600CDCAE1 /* ko */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; CB9C80F219CFAACC00CDCAE1 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; CB9C80F719CFB79700CDCAE1 /* SelfControl Killer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SelfControl Killer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1344,6 +1371,8 @@ CBBF4E941582F8FC00E364D9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; CBBF4E951582F8FC00E364D9 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = ""; }; CBBF4E961582F8FC00E364D9 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; + CBC1F4B226070358008E3FA8 /* SCFileWatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCFileWatcher.h; sourceTree = ""; }; + CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCFileWatcher.m; sourceTree = ""; }; CBC25B1319F6CBDE0013E190 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; CBC25B1919F6CC030013E190 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = ""; }; CBC2F8570F4672FE00CF2A42 /* LaunchctlHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LaunchctlHelper.m; sourceTree = ""; }; @@ -1434,12 +1463,14 @@ CBEE50C00F48C21F00F5DF1C /* TimerWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimerWindowController.m; sourceTree = ""; }; 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 = ""; }; + D0BABA9759C378EE7C619F91 /* 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 = ""; }; + E1139D5A5B92C88FFF62718F /* 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 = ""; }; + E3346B8A670C55C686428776 /* Pods-SelfControl-SelfControlTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl-SelfControlTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests.debug.xcconfig"; sourceTree = ""; }; + EBDF1B23AA44B10313D79203 /* 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 = ""; }; + F0874F1ABF369B21F1CEADC1 /* 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 = ""; }; + F41DEF1E3926B4CF3AE2B76C /* Pods_SelfControl_SelfControlTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl_SelfControlTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 */ @@ -1452,7 +1483,7 @@ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, CB587E500F50FE8800C66A09 /* SystemConfiguration.framework in Frameworks */, CBB3FD7A0F53834B00244132 /* Security.framework in Frameworks */, - ED0A588EF1802EAF639D2925 /* Pods_SelfControl.framework in Frameworks */, + DC4DBA9148D8D67A11899C5E /* Pods_SelfControl.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1460,6 +1491,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 8CA8987104D2956493D6AF6B /* Pods_SelfControl_SelfControlTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1472,7 +1504,7 @@ CBD2677011ED92DE00042CD8 /* CoreFoundation.framework in Frameworks */, CBD2677311ED92EF00042CD8 /* Foundation.framework in Frameworks */, CBD2677511ED92F800042CD8 /* Cocoa.framework in Frameworks */, - 051230B633C4B37DD5CF305E /* libPods-selfcontrol-cli.a in Frameworks */, + 5E6BEEBB5C6E29DADDB344CF /* libPods-selfcontrol-cli.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1484,7 +1516,7 @@ CB74D1162480E506002B2079 /* Security.framework in Frameworks */, CB74D1182480E506002B2079 /* Foundation.framework in Frameworks */, CB74D1192480E506002B2079 /* Cocoa.framework in Frameworks */, - 01EFCB03830117674B4E6B21 /* libPods-org.eyebeam.selfcontrold.a in Frameworks */, + 63BAC9E58A69B15D342B0E29 /* libPods-org.eyebeam.selfcontrold.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1495,7 +1527,7 @@ CB32D2B121902DB300B8CD68 /* IOKit.framework in Frameworks */, CB9C813019CFBBC000CDCAE1 /* Security.framework in Frameworks */, CB9C812F19CFBBB900CDCAE1 /* Cocoa.framework in Frameworks */, - 9A31F3D484C646257DB308C5 /* Pods_SelfControl_Killer.framework in Frameworks */, + E263B809965135813A557CD5 /* Pods_SelfControl_Killer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1507,7 +1539,7 @@ CB9C812C19CFBB8400CDCAE1 /* Cocoa.framework in Frameworks */, CB9C812A19CFBB8000CDCAE1 /* Foundation.framework in Frameworks */, CB9C812819CFBB7B00CDCAE1 /* Security.framework in Frameworks */, - D2C22E71F82365403602D7B8 /* libPods-SCKillerHelper.a in Frameworks */, + D4EDD26C770910569C31D36F /* libPods-SCKillerHelper.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1611,11 +1643,12 @@ CB9C812919CFBB8000CDCAE1 /* Foundation.framework */, 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, - 76753023D2020A5974B2D62C /* Pods_SelfControl.framework */, - 8BA749B3BC24DC33425F27DF /* Pods_SelfControl_Killer.framework */, - F946636EDF2639CE7629BF6C /* libPods-selfcontrol-cli.a */, - 6849B132CC615D050806CDA7 /* libPods-SCKillerHelper.a */, - 6494AB6FC557A9B46A222422 /* libPods-org.eyebeam.selfcontrold.a */, + AF899A50A17F0C6C8C6B84A2 /* libPods-SCKillerHelper.a */, + 6EBDE7B29D92764A409E4FDA /* Pods_SelfControl.framework */, + 86DAD6532C67CBE72E99084C /* Pods_SelfControl_Killer.framework */, + F41DEF1E3926B4CF3AE2B76C /* Pods_SelfControl_SelfControlTests.framework */, + 8BF3973D41997900DF147B24 /* libPods-org.eyebeam.selfcontrold.a */, + C85A094F15E20A0DB58665A8 /* libPods-selfcontrol-cli.a */, ); name = Frameworks; sourceTree = ""; @@ -1635,6 +1668,8 @@ CB81A9E325B7C2B9006956F7 /* Utility */, CBADC27C25B22BC7000EE5BB /* SCSentry.h */, CBADC27D25B22BC7000EE5BB /* SCSentry.m */, + CBC1F4B226070358008E3FA8 /* SCFileWatcher.h */, + CBC1F4B326070358008E3FA8 /* SCFileWatcher.m */, CB81A9F025B7C5F7006956F7 /* SCBlockFileReaderWriter.h */, CB81A9F125B7C5F7006956F7 /* SCBlockFileReaderWriter.m */, CB1465B625B027E700130D2E /* SCErr.h */, @@ -1661,6 +1696,8 @@ CBE5C40A0F4D4531003DB900 /* ButtonWithPopupMenu.m */, CB529BBD0F32B7ED00564FB8 /* AppController.h */, CB529BBE0F32B7ED00564FB8 /* AppController.m */, + CB953112262BC64F000C8309 /* SCDurationSlider.h */, + CB953113262BC64F000C8309 /* SCDurationSlider.m */, CBD4848719D764440020F949 /* PreferencesGeneralViewController.h */, CBD4848819D764440020F949 /* PreferencesGeneralViewController.m */, CBD4848C19D768C90020F949 /* PreferencesAdvancedViewController.h */, @@ -1732,6 +1769,8 @@ CB81AB8925B8E6BE006956F7 /* SCBlockEntry.m */, CBB0AE280FA74566006229B3 /* HostFileBlocker.h */, CBB0AE290FA74566006229B3 /* HostFileBlocker.m */, + CB5888B125F6056400B5C64D /* HostFileBlockerSet.h */, + CB5888B225F6056400B5C64D /* HostFileBlockerSet.m */, CBCA91101960D87300AFD20C /* PacketFilter.h */, CBCA91111960D87300AFD20C /* PacketFilter.m */, CB25806016C1FDBE0059C99A /* BlockManager.h */, @@ -2816,17 +2855,19 @@ DDDC709FF9E9196A96CA7BCF /* Pods */ = { isa = PBXGroup; 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 */, CBB672FF25D61437006E4BC9 /* ArgumentParser */, + D0BABA9759C378EE7C619F91 /* Pods-SCKillerHelper.debug.xcconfig */, + E1139D5A5B92C88FFF62718F /* Pods-SCKillerHelper.release.xcconfig */, + 32B26CAAF2E2B648B3C0E892 /* Pods-SelfControl.debug.xcconfig */, + 5FA850E53EB64C54A1404AEF /* Pods-SelfControl.release.xcconfig */, + 50CA92A5EB2C31792CA00641 /* Pods-SelfControl Killer.debug.xcconfig */, + EBDF1B23AA44B10313D79203 /* Pods-SelfControl Killer.release.xcconfig */, + E3346B8A670C55C686428776 /* Pods-SelfControl-SelfControlTests.debug.xcconfig */, + C72025F2499F9F07E281CB50 /* Pods-SelfControl-SelfControlTests.release.xcconfig */, + 77CFA479BA7C3ECBC15F96D7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */, + F0874F1ABF369B21F1CEADC1 /* Pods-org.eyebeam.selfcontrold.release.xcconfig */, + 64682B3371BA0A5044028058 /* Pods-selfcontrol-cli.debug.xcconfig */, + 69102D5CD4E9672D0EFBF25B /* Pods-selfcontrol-cli.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -2850,6 +2891,7 @@ CB81A94825B7B5B5006956F7 /* SCMigrationUtilities.h in Headers */, CB81AA3A25B7D152006956F7 /* SCHelperToolUtilities.h in Headers */, CB81AAB725B7E6C7006956F7 /* DeprecationSilencers.h in Headers */, + CBC1F4B426070358008E3FA8 /* SCFileWatcher.h in Headers */, CB81A9CF25B7C269006956F7 /* SCBlockUtilities.h in Headers */, CB81A9F225B7C5F7006956F7 /* SCBlockFileReaderWriter.h in Headers */, ); @@ -2862,7 +2904,7 @@ isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "SelfControl" */; buildPhases = ( - 10CA088CE7CF641F4F172445 /* [CP] Check Pods Manifest.lock */, + 54B25F59AEDB053782FAC10D /* [CP] Check Pods Manifest.lock */, CB81A92E25B7B4B8006956F7 /* ShellScript */, 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, @@ -2870,8 +2912,8 @@ CB2359D90F4541AB0030F59C /* Copy Executable Helper Tools */, CBDFFF4624A044C900622CEE /* Copy Daemon Launch Service */, CB5E5FF81C3A5FD10038F331 /* ShellScript */, - 7D55904F6A3D627B837007A9 /* [CP] Embed Pods Frameworks */, CB81AB2725B7EFA4006956F7 /* Embed Frameworks */, + BED0609C15A860C955A3552D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -2887,10 +2929,12 @@ isa = PBXNativeTarget; buildConfigurationList = CB0EEF6420FD8CE00024D27B /* Build configuration list for PBXNativeTarget "SelfControlTests" */; buildPhases = ( + 2A27B423AFFABE56BC018570 /* [CP] Check Pods Manifest.lock */, CB841F05243A6D7F00274FDF /* Headers */, CB0EEF5920FD8CE00024D27B /* Sources */, CB0EEF5A20FD8CE00024D27B /* Frameworks */, CB0EEF5B20FD8CE00024D27B /* Resources */, + ED887BF54A55A00F42A6FD9C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -2906,7 +2950,7 @@ isa = PBXNativeTarget; buildConfigurationList = CB74D11A2480E506002B2079 /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */; buildPhases = ( - 13D18A4223D8447176403B7B /* [CP] Check Pods Manifest.lock */, + D76E3B7390CC3DD10C32CD7F /* [CP] Check Pods Manifest.lock */, CB74D1062480E506002B2079 /* ShellScript */, CB74D1072480E506002B2079 /* Sources */, CB74D1142480E506002B2079 /* Frameworks */, @@ -2924,12 +2968,12 @@ isa = PBXNativeTarget; buildConfigurationList = CB9C811119CFB79700CDCAE1 /* Build configuration list for PBXNativeTarget "SelfControl Killer" */; buildPhases = ( - 43BCF4BDF8B57ECB6640A9A7 /* [CP] Check Pods Manifest.lock */, + C4455A3F0260679D1BD5962E /* [CP] Check Pods Manifest.lock */, CB9C80F319CFB79700CDCAE1 /* Sources */, CB9C80F419CFB79700CDCAE1 /* Frameworks */, CB9C80F519CFB79700CDCAE1 /* Resources */, CB9C813119CFBBD300CDCAE1 /* Copy Helper Tools */, - 4908B8EBDC1C6FDD38B8BC40 /* [CP] Embed Pods Frameworks */, + AF328C5935C794FB31FCD1B5 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -2944,7 +2988,7 @@ isa = PBXNativeTarget; buildConfigurationList = CB9C811F19CFBA8500CDCAE1 /* Build configuration list for PBXNativeTarget "SCKillerHelper" */; buildPhases = ( - FFF74CE7FF453369A49FFE5B /* [CP] Check Pods Manifest.lock */, + 69AC2704461942B09B953173 /* [CP] Check Pods Manifest.lock */, CB9C811719CFBA8500CDCAE1 /* Sources */, CB9C811819CFBA8500CDCAE1 /* Frameworks */, ); @@ -2961,7 +3005,7 @@ isa = PBXNativeTarget; buildConfigurationList = CBA2AFD60F39EC33005AFEBE /* Build configuration list for PBXNativeTarget "selfcontrol-cli" */; buildPhases = ( - 9480FFC8127CE2B36F02552C /* [CP] Check Pods Manifest.lock */, + 667CC580701BF2C2321C3B5D /* [CP] Check Pods Manifest.lock */, CB20C5D6245696C500B9D749 /* ShellScript */, CBA2AFCF0F39EC12005AFEBE /* Sources */, CB54D4490F93E32B00AA22E9 /* Frameworks */, @@ -2984,7 +3028,7 @@ LastUpgradeCheck = 0930; TargetAttributes = { 8D1107260486CEB800E47090 = { - DevelopmentTeam = L6W5L88KN7; + DevelopmentTeam = EG6ZYP3AQH; LastSwiftMigration = 1150; ProvisioningStyle = Manual; SystemCapabilities = { @@ -2995,26 +3039,25 @@ }; CB0EEF5C20FD8CE00024D27B = { CreatedOnToolsVersion = 9.4.1; - DevelopmentTeam = L6W5L88KN7; + DevelopmentTeam = EG6ZYP3AQH; ProvisioningStyle = Automatic; - TestTargetID = 8D1107260486CEB800E47090; }; CB74D1052480E506002B2079 = { - DevelopmentTeam = L6W5L88KN7; + DevelopmentTeam = EG6ZYP3AQH; ProvisioningStyle = Manual; }; CB9C80F619CFB79700CDCAE1 = { CreatedOnToolsVersion = 6.0.1; - DevelopmentTeam = L6W5L88KN7; + DevelopmentTeam = EG6ZYP3AQH; ProvisioningStyle = Automatic; }; CB9C811A19CFBA8500CDCAE1 = { CreatedOnToolsVersion = 6.0.1; - DevelopmentTeam = L6W5L88KN7; + DevelopmentTeam = EG6ZYP3AQH; ProvisioningStyle = Manual; }; CBA2AFD10F39EC12005AFEBE = { - DevelopmentTeam = L6W5L88KN7; + DevelopmentTeam = EG6ZYP3AQH; ProvisioningStyle = Manual; }; }; @@ -3383,16 +3426,42 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 10CA088CE7CF641F4F172445 /* [CP] Check Pods Manifest.lock */ = { + 2A27B423AFFABE56BC018570 /* [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-SelfControlTests-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; + }; + 54B25F59AEDB053782FAC10D /* [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-checkManifestLockResult.txt", ); @@ -3401,7 +3470,7 @@ 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 */ = { + 667CC580701BF2C2321C3B5D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3416,14 +3485,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-org.eyebeam.selfcontrold-checkManifestLockResult.txt", + "$(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; }; - 43BCF4BDF8B57ECB6640A9A7 /* [CP] Check Pods Manifest.lock */ = { + 69AC2704461942B09B953173 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3438,14 +3507,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelfControl Killer-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SCKillerHelper-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 */ = { + AF328C5935C794FB31FCD1B5 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3463,7 +3532,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 7D55904F6A3D627B837007A9 /* [CP] Embed Pods Frameworks */ = { + BED0609C15A860C955A3552D /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3489,7 +3558,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 9480FFC8127CE2B36F02552C /* [CP] Check Pods Manifest.lock */ = { + C4455A3F0260679D1BD5962E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3504,7 +3573,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-selfcontrol-cli-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelfControl Killer-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -3575,7 +3644,7 @@ 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"; }; - FFF74CE7FF453369A49FFE5B /* [CP] Check Pods Manifest.lock */ = { + D76E3B7390CC3DD10C32CD7F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3590,13 +3659,39 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SCKillerHelper-checkManifestLockResult.txt", + "$(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; }; + ED887BF54A55A00F42A6FD9C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests-frameworks.sh", + "${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", + "${BUILT_PRODUCTS_DIR}/TransformerKit/TransformerKit.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${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", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TransformerKit.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -3604,6 +3699,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CBC1F4B526070358008E3FA8 /* SCFileWatcher.m in Sources */, CBADC27E25B22BC7000EE5BB /* SCSentry.m in Sources */, 8D11072D0486CEB800E47090 /* main.m in Sources */, CB81AC2A25B909E1006956F7 /* SCUIUtilities.m in Sources */, @@ -3611,6 +3707,7 @@ CB529BBF0F32B7ED00564FB8 /* AppController.m in Sources */, CB81A94925B7B5B5006956F7 /* SCMigrationUtilities.m in Sources */, CB81A9F325B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, + CB5888DC25F60DC300B5C64D /* HostFileBlockerSet.m in Sources */, CB1465B825B027E700130D2E /* SCErr.m in Sources */, CBB1731920F05C07007FCAE9 /* SCMiscUtilities.m in Sources */, CB58948025B3FC6D00E9A5C0 /* HostFileBlocker.m in Sources */, @@ -3627,6 +3724,7 @@ CBD4848F19D768C90020F949 /* PreferencesAdvancedViewController.m in Sources */, CB81A9D025B7C269006956F7 /* SCBlockUtilities.m in Sources */, CBB7DEEA0F53313F00ABF3EA /* DomainListWindowController.m in Sources */, + CB953114262BC64F000C8309 /* SCDurationSlider.m in Sources */, CBF3B574217BADD7006D5F52 /* SCSettings.m in Sources */, CB25806616C237F10059C99A /* NSString+IPAddress.m in Sources */, ); @@ -3636,10 +3734,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CB066F6C2652037E0076964D /* HostFileBlocker.m in Sources */, + CB066F94265203970076964D /* SCErr.m in Sources */, + CBC1F4B926070358008E3FA8 /* SCFileWatcher.m in Sources */, + CBDAB4F72651FDC900A1951C /* AllowlistScraper.m in Sources */, + CB066F95265203990076964D /* SCSentry.m in Sources */, CBDF919A225C5A9700358B95 /* SCMiscUtilities.m in Sources */, CB5DFCBC2251DD1F0084CEC2 /* SCConstants.m in Sources */, CB81AA3F25B7D152006956F7 /* SCHelperToolUtilities.m in Sources */, CB81A9D425B7C269006956F7 /* SCBlockUtilities.m in Sources */, + CB066F92265203830076964D /* PacketFilter.m in Sources */, + CB066F93265203920076964D /* SCBlockEntry.m in Sources */, + CB066F91265203800076964D /* HostFileBlockerSet.m in Sources */, + CBDAB4D22651FDB000A1951C /* BlockManager.m in Sources */, CB81A9F725B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, CB114284222CD4F0004B7868 /* SCSettings.m in Sources */, CB0EEF7820FE49030024D27B /* SCUtilityTests.m in Sources */, @@ -3660,6 +3767,8 @@ CB74D1202480E566002B2079 /* SCDaemon.m in Sources */, CBADC28225B22BC7000EE5BB /* SCSentry.m in Sources */, CB62FC4024B1327D00ADBC40 /* SCSettings.m in Sources */, + CB5888EA25F60DC500B5C64D /* HostFileBlockerSet.m in Sources */, + CBC1F4BA26070358008E3FA8 /* SCFileWatcher.m in Sources */, CB62FC3F24B1327A00ADBC40 /* SCMiscUtilities.m in Sources */, CB1465BC25B027E700130D2E /* SCErr.m in Sources */, CB81AA4025B7D152006956F7 /* SCHelperToolUtilities.m in Sources */, @@ -3686,6 +3795,7 @@ CB9C80FF19CFB79700CDCAE1 /* AppDelegate.m in Sources */, CB9C80FC19CFB79700CDCAE1 /* main.m in Sources */, CB5DFCBA2251DD1F0084CEC2 /* SCConstants.m in Sources */, + CB5888E325F60DC400B5C64D /* HostFileBlockerSet.m in Sources */, CB81AC2B25B909E1006956F7 /* SCUIUtilities.m in Sources */, CB32D2AC21902CF800B8CD68 /* SCSettings.m in Sources */, CB81A9F525B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, @@ -3695,6 +3805,7 @@ CB81AB8C25B8E6BE006956F7 /* SCBlockEntry.m in Sources */, CBB1731C20F05C0A007FCAE9 /* SCMiscUtilities.m in Sources */, CBADC28025B22BC7000EE5BB /* SCSentry.m in Sources */, + CBC1F4B726070358008E3FA8 /* SCFileWatcher.m in Sources */, CB81A94B25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3718,10 +3829,12 @@ CB81A9D325B7C269006956F7 /* SCBlockUtilities.m in Sources */, CB9C812319CFBB4400CDCAE1 /* LaunchctlHelper.m in Sources */, CB1CA64D25ABA5BB0084A551 /* SCXPCAuthorization.m in Sources */, + CBC1F4B826070358008E3FA8 /* SCFileWatcher.m in Sources */, CB9C812219CFBB3800CDCAE1 /* PacketFilter.m in Sources */, CB1465BB25B027E700130D2E /* SCErr.m in Sources */, CBB1731B20F05C09007FCAE9 /* SCMiscUtilities.m in Sources */, CB81A9F625B7C5F7006956F7 /* SCBlockFileReaderWriter.m in Sources */, + CB5888E425F60DC500B5C64D /* HostFileBlockerSet.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3738,6 +3851,7 @@ CB1CA64E25ABA5BB0084A551 /* SCXPCAuthorization.m in Sources */, CB81A9D125B7C269006956F7 /* SCBlockUtilities.m in Sources */, CBB67D5C25D6165B006E4BC9 /* NSArray+XPMArgumentsNormalizer.m in Sources */, + CB5888E225F60DC300B5C64D /* HostFileBlockerSet.m in Sources */, CBB67D5D25D6165B006E4BC9 /* NSDictionary+RubyDescription.m in Sources */, CBB67D5425D6165B006E4BC9 /* XPMArgsKonstants.m in Sources */, CBB67D5325D6165B006E4BC9 /* XPMMutableAttributedArray.m in Sources */, @@ -3757,6 +3871,7 @@ CBB0AE2A0FA74566006229B3 /* HostFileBlocker.m in Sources */, CB81A94A25B7B5B6006956F7 /* SCMigrationUtilities.m in Sources */, CB32D2A921902CB300B8CD68 /* SCSettings.m in Sources */, + CBC1F4B626070358008E3FA8 /* SCFileWatcher.m in Sources */, CB1CA65025ABA5BB0084A551 /* SCXPCClient.m in Sources */, CB25806216C1FDBE0059C99A /* BlockManager.m in Sources */, CB25806716C237F10059C99A /* NSString+IPAddress.m in Sources */, @@ -3937,16 +4052,15 @@ /* Begin XCBuildConfiguration section */ C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DBA01017E61D6B843CA3758D /* Pods-SelfControl.debug.xcconfig */; + baseConfigurationReference = 32B26CAAF2E2B648B3C0E892 /* Pods-SelfControl.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 403; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -3959,7 +4073,6 @@ INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MARKETING_VERSION = 3.9.9; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.${PRODUCT_NAME:identifier}"; PRODUCT_NAME = SelfControl; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3972,14 +4085,14 @@ }; C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5F489F28028DC38C046B36D3 /* Pods-SelfControl.release.xcconfig */; + baseConfigurationReference = 5FA850E53EB64C54A1404AEF /* Pods-SelfControl.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 403; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -3991,7 +4104,6 @@ INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MARKETING_VERSION = 3.9.9; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.${PRODUCT_NAME:identifier}"; PRODUCT_NAME = SelfControl; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4027,7 +4139,7 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; + DEVELOPMENT_TEAM = EG6ZYP3AQH; ENABLE_HARDENED_RUNTIME = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -4042,6 +4154,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 4.0.2; ONLY_ACTIVE_ARCH = YES; OTHER_CODE_SIGN_FLAGS = "-o library,hard,kill,runtime"; SDKROOT = macosx; @@ -4076,7 +4189,7 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; + DEVELOPMENT_TEAM = EG6ZYP3AQH; ENABLE_HARDENED_RUNTIME = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -4089,6 +4202,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 4.0.2; ONLY_ACTIVE_ARCH = NO; OTHER_CODE_SIGN_FLAGS = "-o library,hard,kill,runtime"; SDKROOT = macosx; @@ -4099,8 +4213,8 @@ }; CB0EEF6520FD8CE00024D27B /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E3346B8A670C55C686428776 /* Pods-SelfControl-SelfControlTests.debug.xcconfig */; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -4112,10 +4226,8 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -4129,19 +4241,16 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SelfControlTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = org.selfcontrolapp.SelfControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SelfControl.app/Contents/MacOS/SelfControl"; }; name = Debug; }; CB0EEF6620FD8CE00024D27B /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = C72025F2499F9F07E281CB50 /* Pods-SelfControl-SelfControlTests.release.xcconfig */; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -4153,9 +4262,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREFIX_HEADER = SelfControl_Prefix.pch; @@ -4163,27 +4270,23 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SelfControlTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = org.selfcontrolapp.SelfControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SelfControl.app/Contents/MacOS/SelfControl"; }; name = Release; }; CB74D11B2480E506002B2079 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 92E88BFAB5B19695DD021D54 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */; + baseConfigurationReference = 77CFA479BA7C3ECBC15F96D7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 403; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; GCC_DYNAMIC_NO_PIC = NO; GCC_MODEL_TUNING = G5; GCC_NO_COMMON_BLOCKS = NO; @@ -4193,7 +4296,6 @@ GCC_VERSION = ""; INFOPLIST_FILE = "Daemon/selfcontrold-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 3.9.9; OTHER_LDFLAGS = ( "-sectcreate", __TEXT, @@ -4218,16 +4320,15 @@ }; CB74D11C2480E506002B2079 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B1812886BF1D4F103F3E8D1D /* Pods-org.eyebeam.selfcontrold.release.xcconfig */; + baseConfigurationReference = F0874F1ABF369B21F1CEADC1 /* Pods-org.eyebeam.selfcontrold.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 403; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; GCC_MODEL_TUNING = G5; GCC_NO_COMMON_BLOCKS = NO; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -4235,7 +4336,6 @@ GCC_VERSION = ""; INFOPLIST_FILE = "Daemon/selfcontrold-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 3.9.9; OTHER_LDFLAGS = ( "-sectcreate", __TEXT, @@ -4260,7 +4360,7 @@ }; CB9C811219CFB79700CDCAE1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 40D07D2167368D5ED474897D /* Pods-SelfControl Killer.debug.xcconfig */; + baseConfigurationReference = 50CA92A5EB2C31792CA00641 /* Pods-SelfControl Killer.debug.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4275,11 +4375,9 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 403; - DEVELOPMENT_TEAM = ""; + CURRENT_PROJECT_VERSION = 410; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -4297,7 +4395,6 @@ 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"; @@ -4308,7 +4405,7 @@ }; CB9C811319CFB79700CDCAE1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 51978917EB66684CE8DAD8C7 /* Pods-SelfControl Killer.release.xcconfig */; + baseConfigurationReference = EBDF1B23AA44B10313D79203 /* Pods-SelfControl Killer.release.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4325,8 +4422,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 403; - DEVELOPMENT_TEAM = L6W5L88KN7; + CURRENT_PROJECT_VERSION = 410; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -4338,7 +4434,6 @@ 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)"; @@ -4348,7 +4443,7 @@ }; CB9C812019CFBA8500CDCAE1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5D0CD260CB742C9423EB8A83 /* Pods-SCKillerHelper.debug.xcconfig */; + baseConfigurationReference = D0BABA9759C378EE7C619F91 /* Pods-SCKillerHelper.debug.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4363,8 +4458,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; - DEVELOPMENT_TEAM = L6W5L88KN7; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -4392,7 +4486,7 @@ }; CB9C812119CFBA8500CDCAE1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 04A4C3645D49A017F4B89A0F /* Pods-SCKillerHelper.release.xcconfig */; + baseConfigurationReference = E1139D5A5B92C88FFF62718F /* Pods-SCKillerHelper.release.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4407,8 +4501,8 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; COPY_PHASE_STRIP = YES; - DEVELOPMENT_TEAM = L6W5L88KN7; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -4429,16 +4523,15 @@ }; CBA2AFD40F39EC14005AFEBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EA6CDF7FADBBC7B9A7694C8B /* Pods-selfcontrol-cli.debug.xcconfig */; + baseConfigurationReference = 64682B3371BA0A5044028058 /* Pods-selfcontrol-cli.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; COPY_PHASE_STRIP = NO; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; - CURRENT_PROJECT_VERSION = 403; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n", ); @@ -4452,7 +4545,6 @@ INFOPLIST_FILE = "selfcontrol-cli-Info.plist"; INFOPLIST_PREPROCESS = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 3.9.9; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.selfcontrol-cli"; PRODUCT_NAME = "selfcontrol-cli"; PROVISIONING_PROFILE = ""; @@ -4466,15 +4558,15 @@ }; CBA2AFD50F39EC14005AFEBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F2797D75520FA7BABC7C2B07 /* Pods-selfcontrol-cli.release.xcconfig */; + baseConfigurationReference = 69102D5CD4E9672D0EFBF25B /* Pods-selfcontrol-cli.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; COPY_PHASE_STRIP = NO; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; - CURRENT_PROJECT_VERSION = 403; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6W5L88KN7; FRAMEWORK_SEARCH_PATHS = ( "$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n", ); @@ -4486,7 +4578,6 @@ INFOPLIST_FILE = "selfcontrol-cli-Info.plist"; INFOPLIST_PREPROCESS = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 3.9.9; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.selfcontrol-cli"; PRODUCT_NAME = "selfcontrol-cli"; PROVISIONING_PROFILE = ""; diff --git a/SelfControlTests/Info.plist b/SelfControlTests/Info.plist index f90a532f..02c071c7 100644 --- a/SelfControlTests/Info.plist +++ b/SelfControlTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 4.0 alpha 2 + 4.0.2 CFBundleVersion - 403 + 410 diff --git a/TimerWindowController.h b/TimerWindowController.h index 7f18ee20..fcccfb6b 100755 --- a/TimerWindowController.h +++ b/TimerWindowController.h @@ -22,6 +22,7 @@ #import #import "AppController.h" +#import "SCDurationSlider.h" // A subclass of NSWindowController created to manage the floating timer window // which tells the user how much time remains in the block. @@ -36,14 +37,14 @@ IBOutlet NSButton* addToBlockButton_; IBOutlet NSButton* killBlockButton_; IBOutlet NSButton* extendBlockButton_; + IBOutlet NSTextField* legacyBlockWarningLabel_; IBOutlet NSPanel* addSheet_; IBOutlet NSTextField* addToBlockTextField_; IBOutlet NSPanel* extendBlockTimeSheet_; - - IBOutlet NSTextField* extendBlockHoursField_; - IBOutlet NSTextField* extendBlockMinutesField_; + IBOutlet SCDurationSlider* extendDurationSlider_; + IBOutlet NSTextField* extendDurationLabel_; } @@ -58,6 +59,9 @@ // Closes the "Extend Block Time" sheet. - (IBAction) closeExtendSheet:(id)sender; +// updates the preview label showing the extension duration +- (IBAction)updateExtendSliderDisplay:(id)sender; + // Called when the "Add to Block" button is clicked, instantiates and runs a sheet // to take input for the host to block. - (IBAction) addToBlock:(id)sender; diff --git a/TimerWindowController.m b/TimerWindowController.m index a2100454..03fcb6c2 100755 --- a/TimerWindowController.m +++ b/TimerWindowController.m @@ -75,6 +75,10 @@ - (void)awakeFromNib { killBlockButton_.hidden = YES; addToBlockButton_.hidden = NO; extendBlockButton_.hidden = NO; + legacyBlockWarningLabel_.hidden = YES; + + // set up extend block dialog + extendDurationSlider_.maxDuration = [defaults integerForKey: @"MaxBlockLength"]; if ([SCBlockUtilities modernBlockIsRunning]) { blockEndingDate_ = [settings_ valueForKey: @"BlockEndDate"]; @@ -86,8 +90,9 @@ - (void)awakeFromNib { // 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; + addToBlockButton_.hidden = YES; + extendBlockButton_.hidden = YES; + legacyBlockWarningLabel_.hidden = NO; } } @@ -152,21 +157,22 @@ - (void)updateTimerDisplay:(NSTimer*)timer { if(numSeconds < 0 && [timerLabel_.stringValue isEqualToString: finishingString]) { [[NSApp dockTile] setBadgeLabel: nil]; - // This increments the strike counter. After ten strikes of the timer being + // This increments the strike counter. After four strikes of the timer being // at or less than 0 seconds, SelfControl will assume something's wrong and enable // manual block removal numStrikes++; - if(numStrikes > 10) { + if(numStrikes >= 7) { // 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 == 7) { + NSLog(@"WARNING: Block should have ended! Probable failure to remove."); NSError* err = [SCErr errorWithCode: 105]; [SCSentry captureError: err]; } addToBlockButton_.hidden = YES; extendBlockButton_.hidden = YES; + legacyBlockWarningLabel_.hidden = YES; killBlockButton_.hidden = NO; } @@ -219,7 +225,7 @@ - (void)updateTimerDisplay:(NSTimer*)timer { // make sure add to list is disabled if it's an allowlist block // 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"]; + addToBlockButton_.enabled = ![settings_ boolForKey: @"ActiveBlockAsWhitelist"]; } } @@ -258,6 +264,16 @@ - (IBAction) extendBlockTime:(id)sender { [modifyBlockLock unlock]; } +- (IBAction)updateExtendSliderDisplay:(id)sender { + // if the duration is larger than we can display on our slider + // chop it down to our max display value so the user doesn't + // accidentally extend the block much longer than intended + if (extendDurationSlider_.durationValueMinutes > extendDurationSlider_.maxDuration) { + extendDurationSlider_.integerValue = extendDurationSlider_.maxDuration; + } + + extendDurationLabel_.stringValue = extendDurationSlider_.durationDescription; +} - (IBAction) closeAddSheet:(id)sender { [NSApp endSheet: addSheet_]; @@ -269,11 +285,12 @@ - (IBAction) closeExtendSheet:(id)sender { - (IBAction) performAddSite:(id)sender { NSString* addToBlockTextFieldContents = [addToBlockTextField_ stringValue]; [self.appController addToBlockList: addToBlockTextFieldContents lock: modifyBlockLock]; + addToBlockTextField_.stringValue = @""; // clear text field for next time [NSApp endSheet: addSheet_]; } - (IBAction) performExtendBlock:(id)sender { - NSInteger extendBlockMinutes = (extendBlockHoursField_.intValue * 60) + extendBlockMinutesField_.intValue; + NSInteger extendBlockMinutes = extendDurationSlider_.durationValueMinutes; [self.appController extendBlockTime: extendBlockMinutes lock: modifyBlockLock]; [NSApp endSheet: extendBlockTimeSheet_]; @@ -339,8 +356,11 @@ - (IBAction)killBlock:(id)sender { char uidString[10]; snprintf(uidString, sizeof(uidString), "%d", getuid()); - char* args[] = { uidString, NULL }; + NSDate* keyDate = [NSDate date]; + NSString* killerKey = [SCMiscUtilities killerKeyForDate: keyDate]; + NSString* keyDateString = [[NSISO8601DateFormatter new] stringFromDate: keyDate]; + char* args[] = { (char*)[killerKey UTF8String], (char*)[keyDateString UTF8String], uidString, NULL }; FILE* pipe = NULL; status = AuthorizationExecuteWithPrivileges(authorizationRef, @@ -352,13 +372,16 @@ - (IBAction)killBlock:(id)sender { if(status) { NSLog(@"WARNING: Authorized execution of helper tool returned failure status code %d", status); - NSError* err = [SCErr errorWithCode: 400]; - [SCSentry captureError: err]; - [SCUIUtilities presentError: err]; + /// 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]; + [SCUIUtilities presentError: err]; + } return; } - + // read until the pipe finishes so we wait for execution to end before we // show the modal (this also helps make the focus ordering better) for (;;) { @@ -377,17 +400,25 @@ - (IBAction)killBlock:(id)sender { waitUntilDone:YES]; // send some debug info to Sentry to help us track this issue + // detailed logs disabled for now because the best current method might collect user PII we don't want [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]; -// }]; + // [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."]; - [alert addButtonWithTitle: @"OK"]; - [alert runModal]; + if ([SCBlockUtilities anyBlockIsRunning]) { + // ruh roh! the block wasn't cleared successfully, since it's still running + NSError* err = [SCErr errorWithCode: 401]; + [SCSentry captureError: err]; + [SCUIUtilities presentError: err]; + } else { + 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."]; + [alert addButtonWithTitle: @"OK"]; + [alert runModal]; + } } - (NSString*)selfControlKillerHelperToolPath { diff --git a/da.lproj/FirstTime.xib b/da.lproj/FirstTime.xib index 80e30772..761e821f 100644 --- a/da.lproj/FirstTime.xib +++ b/da.lproj/FirstTime.xib @@ -1,8 +1,8 @@ - + - + @@ -13,11 +13,11 @@
- + - + @@ -25,14 +25,15 @@ - + - + - + + @@ -41,6 +42,7 @@ Cg + @@ -86,7 +88,7 @@ Cg - + @@ -135,7 +137,7 @@ Cg Cg - + @@ -184,6 +186,7 @@ Cg Cg + @@ -231,6 +234,7 @@ Cg Hurtig Start Guide + @@ -281,6 +285,7 @@ Cg Cg + @@ -328,6 +333,7 @@ Cg 1. + @@ -377,6 +383,7 @@ Cg + @@ -428,6 +435,7 @@ Cg og tilføj domænenavnene på de mest forstyrrende hjemmesider -- for eksempel "facebook.com". Eller benyt import funktionen til at tilføje en forud oprettet liste af hjemmesider. 2. + @@ -477,6 +485,7 @@ Cg + @@ -525,9 +534,10 @@ Cg - for at beslutte hvor længe du ønsker at blokere disse hjemmesider. Du kan ændre det fra 15 minutter og helt op til 24 timer. + for at beslutte hvor længe du ønsker at blokere disse hjemmesider. Du kan ændre det fra 1 minutter og helt op til 24 timer. 3. + @@ -575,8 +585,9 @@ Cg - + + @@ -628,6 +639,7 @@ Cg indtast dit kodeord for at starte blokeringen, og begynd så at arbejde! + @@ -680,6 +692,7 @@ Cg CgoKA + @@ -729,6 +742,7 @@ CgoKA Cg + @@ -775,6 +789,7 @@ Cg + @@ -820,6 +835,7 @@ Cg + @@ -868,7 +884,6 @@ Cg - -